logstash-integration-jdbc 5.2.6 → 5.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c9cc41a07a8cb2f7e4b9a6993dd4d0cd69761b3fa95ba81238d0dd6267367311
4
- data.tar.gz: 32866a340d87d91734586b3324a1349437abc624deb6ff9883ab998b0a4524a6
3
+ metadata.gz: 28cba197157488c1839fc9a948c3f27916b26af07f28c592f537434bd84722a6
4
+ data.tar.gz: fe2263ea7ed36abdeb9fd3d9597958724be77a8653950e9d554a212c1e3dd106
5
5
  SHA512:
6
- metadata.gz: b3513ba2d9ade38a829ec0b0c47ad6c0978d0256e03fb4ac26e136a064db65b08a19b7ee8dd7dabe26851739877b82f0f9a550b2328473b8ffd62f194d569ebb
7
- data.tar.gz: bf71a208f07bc00c213724cbcf175a9ede2f87bf4eaf85a926ee8e9f87b322e2dd79164db1db84ec60633a0b1cfa961e8fc6d3889c8ca25ea220fa437285af5d
6
+ metadata.gz: 76543ead6834631efaca25d154abe7ee7594943dff0c27d90e7588ed3aa651de427fc3372cffac348f6f810f11cfe13864d8f26af7d09fd34470491a6a4c66b3
7
+ data.tar.gz: de71ae5f8c54dfa08d67e1e848e59fc1779477c41246cee120debf633301829e931b7103546de0bef482002eb7b5296b748d6ad6caf0bb5457abe7205db79b6e
data/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ ## 5.4.0
2
+ - Ambiguous Timestamp Support [#92](https://github.com/logstash-plugins/logstash-integration-jdbc/pull/92)
3
+ - FIX: when encountering an ambiguous timestamp, the JDBC Input no longer crashes
4
+ - Added support for disambiguating timestamps in daylight saving time (DST) overlap periods
5
+
6
+ ## 5.3.0
7
+ - Refactor: start using scheduler mixin [#110](https://github.com/logstash-plugins/logstash-integration-jdbc/pull/110)
8
+
1
9
  ## 5.2.6
2
10
  - Fix: change default path of 'last_run_metadata_path' to be rooted in the LS data.path folder and not in $HOME [#106](https://github.com/logstash-plugins/logstash-integration-jdbc/pull/106)
3
11
 
@@ -21,7 +21,7 @@ include::{include_path}/plugin_header-integration.asciidoc[]
21
21
 
22
22
  ==== Description
23
23
 
24
- This filter executes a SQL query and store the result set in the field
24
+ This filter executes a SQL query and stores the result set in the field
25
25
  specified as `target`.
26
26
  It will cache the results locally in an LRU cache with expiry.
27
27
 
@@ -286,10 +286,14 @@ JDBC connection string
286
286
  ===== `jdbc_default_timezone`
287
287
 
288
288
  * Value type is <<string,string>>
289
+ ** Value should be a canonical timezone or offset, such as `Europe/Paris` or `Etc/GMT+3`
290
+ ** Value _may_ include square-bracketed extensions, such as `America/Denver[dst_enabled_on_overlap:true]`
289
291
  * There is no default value for this setting.
290
292
 
291
- Timezone conversion.
292
- Logstash (and Elasticsearch) expects that timestamps are expressed in UTC terms.
293
+ [id="plugins-{type}s-{plugin}-jdbc_timezone_conv"]
294
+ ====== Timezone conversion
295
+
296
+ Logstash and Elasticsearch expect timestamps to be expressed in UTC terms.
293
297
  If your database has recorded timestamps that are relative to another timezone,
294
298
  the database timezone if you will, then set this setting to be the timezone that
295
299
  the database is using. However, as SQL does not allow for timezone data in
@@ -299,7 +303,15 @@ in relative UTC time in ISO8601 format.
299
303
 
300
304
  Using this setting will manually assign a specified timezone offset, instead
301
305
  of using the timezone setting of the local machine. You must use a canonical
302
- timezone, *America/Denver*, for example.
306
+ timezone, `America/Denver`, for example.
307
+
308
+ [id="plugins-{type}s-{plugin}-jdbc_ambiguous_timestamps"]
309
+ ===== Ambiguous timestamps
310
+
311
+ While it is common to store local times in SQL's timestamp column type, many timezones change their offset during the course of a calendar year and therefore cannot be used with SQL's timestamp type to represent an ordered, continuous timeline.
312
+ For example in the `America/Chicago` zone when daylight saving time (DST) ends in the autumn, the clock rolls from `01:59:59` back to `01:00:00`, making any timestamp in the 2-hour period between `01:00:00CDT` and `02:00:00CST` on that day ambiguous.
313
+
314
+ When encountering an ambiguous timestamp caused by a DST transition, the query will fail unless the timezone specified here includes a square-bracketed instruction for how to handle overlapping periods (such as: `America/Chicago[dst_enabled_on_overlap:true]` or `Australia/Melbourne[dst_enabled_on_overlap:false]`).
303
315
 
304
316
  [id="plugins-{type}s-{plugin}-plugin_timezone"]
305
317
  ===== `plugin_timezone`
@@ -3,7 +3,7 @@ require "logstash-integration-jdbc_jars"
3
3
  require "logstash/filters/base"
4
4
  require "logstash/namespace"
5
5
  require "logstash/plugin_mixins/ecs_compatibility_support"
6
- require "logstash/plugin_mixins/jdbc/scheduler"
6
+ require "logstash/plugin_mixins/scheduler"
7
7
  require_relative "jdbc/loader"
8
8
  require_relative "jdbc/loader_schedule"
9
9
  require_relative "jdbc/repeating_load_runner"
@@ -19,6 +19,8 @@ module LogStash module Filters class JdbcStatic < LogStash::Filters::Base
19
19
  # adds ecs_compatibility config which could be :disabled or :v1
20
20
  include LogStash::PluginMixins::ECSCompatibilitySupport(:disabled, :v1, :v8 => :v1)
21
21
 
22
+ include LogStash::PluginMixins::Scheduler
23
+
22
24
  config_name "jdbc_static"
23
25
 
24
26
  # Define the loaders, an Array of Hashes, to fetch remote data and create local tables.
@@ -162,7 +164,6 @@ module LogStash module Filters class JdbcStatic < LogStash::Filters::Base
162
164
  end
163
165
 
164
166
  def close
165
- @scheduler.stop if @scheduler
166
167
  @parsed_loaders.each(&:close)
167
168
  @processor.close
168
169
  end
@@ -208,13 +209,7 @@ module LogStash module Filters class JdbcStatic < LogStash::Filters::Base
208
209
  if @loader_schedule
209
210
  @loader_runner = Jdbc::RepeatingLoadRunner.new(*runner_args)
210
211
  @loader_runner.initial_load
211
- @scheduler = LogStash::PluginMixins::Jdbc::Scheduler.
212
- start_cron_scheduler(@loader_schedule, thread_name: "[#{id}]-jdbc_static__scheduler") { @loader_runner.repeated_load }
213
- cron_job = @scheduler.cron_jobs.first
214
- if cron_job
215
- frequency = cron_job.respond_to?(:rough_frequency) ? cron_job.rough_frequency : cron_job.frequency
216
- logger.info("Loaders will execute every #{frequency} seconds", loader_schedule: @loader_schedule)
217
- end
212
+ scheduler.cron(@loader_schedule) { @loader_runner.repeated_load }
218
213
  else
219
214
  @loader_runner = Jdbc::SingleLoadRunner.new(*runner_args)
220
215
  @loader_runner.initial_load
@@ -3,12 +3,11 @@ require "logstash/inputs/base"
3
3
  require "logstash/namespace"
4
4
  require "logstash/plugin_mixins/jdbc/common"
5
5
  require "logstash/plugin_mixins/jdbc/jdbc"
6
- require "logstash/plugin_mixins/jdbc/scheduler"
7
6
  require "logstash/plugin_mixins/ecs_compatibility_support"
8
7
  require "logstash/plugin_mixins/ecs_compatibility_support/target_check"
9
8
  require "logstash/plugin_mixins/validator_support/field_reference_validation_adapter"
10
-
11
9
  require "logstash/plugin_mixins/event_support/event_factory_adapter"
10
+ require "logstash/plugin_mixins/scheduler"
12
11
  require "fileutils"
13
12
 
14
13
  # this require_relative returns early unless the JRuby version is between 9.2.0.0 and 9.2.8.0
@@ -147,6 +146,8 @@ module LogStash module Inputs class Jdbc < LogStash::Inputs::Base
147
146
  # adds :field_reference validator adapter
148
147
  extend LogStash::PluginMixins::ValidatorSupport::FieldReferenceValidationAdapter
149
148
 
149
+ include LogStash::PluginMixins::Scheduler
150
+
150
151
  config_name "jdbc"
151
152
 
152
153
  # If undefined, Logstash will complain, even if codec is unused.
@@ -260,7 +261,6 @@ module LogStash module Inputs class Jdbc < LogStash::Inputs::Base
260
261
  end
261
262
  end
262
263
 
263
- require "rufus/scheduler"
264
264
  prepare_jdbc_connection
265
265
 
266
266
  if @use_column_value
@@ -319,22 +319,16 @@ module LogStash module Inputs class Jdbc < LogStash::Inputs::Base
319
319
  def run(queue)
320
320
  load_driver
321
321
  if @schedule
322
- # input thread (Java) name example "[my-oracle]<jdbc"
323
- @scheduler = LogStash::PluginMixins::Jdbc::Scheduler.
324
- start_cron_scheduler(@schedule, thread_name: "[#{id}]<jdbc__scheduler") { execute_query(queue) }
325
- @scheduler.join
322
+ # scheduler input thread name example: "[my-oracle]|input|jdbc|scheduler"
323
+ scheduler.cron(@schedule) { execute_query(queue) }
324
+ scheduler.join
326
325
  else
327
326
  execute_query(queue)
328
327
  end
329
328
  end # def run
330
329
 
331
- def close
332
- @scheduler.shutdown if @scheduler
333
- end
334
-
335
330
  def stop
336
331
  close_jdbc_connection
337
- @scheduler.shutdown(:wait) if @scheduler
338
332
  end
339
333
 
340
334
  private
@@ -4,6 +4,7 @@ require "logstash/config/mixin"
4
4
  require "time"
5
5
  require "date"
6
6
  require_relative "value_tracking"
7
+ require_relative "timezone_proxy"
7
8
  require_relative "statement_handler"
8
9
 
9
10
  java_import java.util.concurrent.locks.ReentrantLock
@@ -82,7 +83,8 @@ module LogStash module PluginMixins module Jdbc
82
83
  # Using this setting will manually assign a specified timezone offset, instead
83
84
  # of using the timezone setting of the local machine. You must use a canonical
84
85
  # timezone, *America/Denver*, for example.
85
- config :jdbc_default_timezone, :validate => :string
86
+ config :jdbc_default_timezone, :validate => :jdbc_timezone_spec
87
+ extend TimezoneProxy::JDBCTimezoneSpecValidator
86
88
 
87
89
  # General/Vendor-specific Sequel configuration options.
88
90
  #
@@ -157,7 +159,7 @@ module LogStash module PluginMixins module Jdbc
157
159
  @database.extension(:pagination)
158
160
  if @jdbc_default_timezone
159
161
  @database.extension(:named_timezones)
160
- @database.timezone = @jdbc_default_timezone
162
+ @database.timezone = TimezoneProxy.load(@jdbc_default_timezone)
161
163
  end
162
164
  if @jdbc_validate_connection
163
165
  @database.extension(:connection_validator)
@@ -218,7 +220,10 @@ module LogStash module PluginMixins module Jdbc
218
220
  yield extract_values_from(row)
219
221
  end
220
222
  success = true
221
- rescue Sequel::DatabaseConnectionError, Sequel::DatabaseError, Java::JavaSql::SQLException => e
223
+ rescue Sequel::DatabaseConnectionError,
224
+ Sequel::DatabaseError,
225
+ Sequel::InvalidValue,
226
+ Java::JavaSql::SQLException => e
222
227
  details = { exception: e.class, message: e.message }
223
228
  details[:cause] = e.cause.inspect if e.cause
224
229
  details[:backtrace] = e.backtrace if @logger.debug?
@@ -0,0 +1,61 @@
1
+ # encoding: utf-8
2
+
3
+ require 'tzinfo'
4
+
5
+ module LogStash module PluginMixins module Jdbc
6
+ ##
7
+ # This `TimezoneProxy` allows timezone specs to include extensions indicating preference for ambiguous handling.
8
+ # @see TimezoneProxy::parse
9
+ module TimezoneProxy
10
+ ##
11
+ # @param timezone_spec [String]: a timezone spec, consisting of any valid timezone identifier
12
+ # followed by square-bracketed extensions. Currently-supported
13
+ # extensions are:
14
+ # `dst_enabled_on_overlap:(true|false)`: when encountering an ambiguous time
15
+ # due to daylight-savings transition,
16
+ # assume DST to be either enabled or
17
+ # disabled instead of raising an
18
+ # AmbiguousTime exception
19
+ # @return [TZInfo::Timezone]
20
+ def self.load(timezone_spec)
21
+ # re-load pass-through
22
+ return timezone_spec if timezone_spec.kind_of?(::TZInfo::Timezone)
23
+
24
+ parsed_spec = /\A(?<name>[^\[]+)(\[(?<extensions>[^\]]*)\])?\z/.match(timezone_spec)
25
+
26
+ timezone = ::TZInfo::Timezone.get(parsed_spec[:name])
27
+ return timezone unless parsed_spec[:extensions]
28
+
29
+ parsed_spec[:extensions].split(';').each do |extension_spec|
30
+ timezone = case extension_spec
31
+ when 'dst_enabled_on_overlap:true' then timezone.dup.extend(PeriodForLocalWithDSTPreference::ON)
32
+ when 'dst_enabled_on_overlap:false' then timezone.dup.extend(PeriodForLocalWithDSTPreference::OFF)
33
+ else fail(ArgumentError, "Invalid timezone extension `#{extension_spec}`")
34
+ end
35
+ end
36
+
37
+ timezone
38
+ end
39
+
40
+ module JDBCTimezoneSpecValidator
41
+ def validate_value(value, validator_name)
42
+ return super(value, validator_name) unless validator_name == :jdbc_timezone_spec
43
+
44
+ [true, TimezoneProxy.load(value)] rescue [false, $!.message]
45
+ end
46
+ end
47
+
48
+ ##
49
+ # @api private
50
+ class PeriodForLocalWithDSTPreference < Module
51
+ def initialize(default_dst_enabled_on_overlap)
52
+ define_method(:period_for_local) do |localtime, dst_enabled_on_overlap=nil, &dismabiguation_block|
53
+ super(localtime, dst_enabled_on_overlap.nil? ? default_dst_enabled_on_overlap : dst_enabled_on_overlap, &dismabiguation_block)
54
+ end
55
+ end
56
+
57
+ ON = new(true)
58
+ OFF = new(false)
59
+ end
60
+ end
61
+ end; end; end
@@ -14,7 +14,7 @@ module LogStash module PluginMixins module Jdbc
14
14
  # use this irrespective of the jdbc_default_timezone setting
15
15
  NumericValueTracker.new(handler)
16
16
  else
17
- if plugin.jdbc_default_timezone.nil? || plugin.jdbc_default_timezone.empty?
17
+ if plugin.jdbc_default_timezone.nil?
18
18
  # no TZ stuff for Sequel, use Time
19
19
  TimeValueTracker.new(handler)
20
20
  else
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'logstash-integration-jdbc'
3
- s.version = '5.2.6'
3
+ s.version = '5.4.0'
4
4
  s.licenses = ['Apache License (2.0)']
5
5
  s.summary = "Integration with JDBC - input and filter plugins"
6
6
  s.description = "This gem is a Logstash plugin required to be installed on top of the Logstash core pipeline using $LS_HOME/bin/logstash-plugin install gemname. This gem is not a stand-alone program"
@@ -34,12 +34,11 @@ Gem::Specification.new do |s|
34
34
 
35
35
  s.add_runtime_dependency 'tzinfo'
36
36
  s.add_runtime_dependency 'tzinfo-data'
37
- # plugin maintains compatibility with < 3.5 (3.0.9)
38
- # but works with newer rufus-scheduler >= 3.5 as well
39
- s.add_runtime_dependency 'rufus-scheduler'
37
+
40
38
  s.add_runtime_dependency 'logstash-mixin-ecs_compatibility_support', '~>1.3'
41
39
  s.add_runtime_dependency "logstash-mixin-validator_support", '~> 1.0'
42
40
  s.add_runtime_dependency "logstash-mixin-event_support", '~> 1.0'
41
+ s.add_runtime_dependency "logstash-mixin-scheduler", '~> 1.0'
43
42
 
44
43
  s.add_development_dependency "childprocess"
45
44
  s.add_development_dependency 'logstash-devutils', '>= 2.3'
@@ -251,18 +251,6 @@ describe LogStash::Inputs::Jdbc do
251
251
  Timecop.return
252
252
  end
253
253
 
254
- it "cleans up scheduler resources on close" do
255
- runner = Thread.new do
256
- plugin.run(queue)
257
- end
258
- sleep 1
259
- plugin.do_close
260
-
261
- scheduler = plugin.instance_variable_get(:@scheduler)
262
- expect(scheduler).to_not be_nil
263
- expect(scheduler.down?).to be_truthy
264
- end
265
-
266
254
  end
267
255
 
268
256
  context "when scheduling and previous runs are to be preserved" do
@@ -708,7 +696,7 @@ describe LogStash::Inputs::Jdbc do
708
696
  "last_run_metadata_path" => Stud::Temporary.pathname }
709
697
  end
710
698
 
711
- let(:nums) { [BigDecimal.new(10), BigDecimal.new(20), BigDecimal.new(30), BigDecimal.new(40), BigDecimal.new(50)] }
699
+ let(:nums) { [BigDecimal(10), BigDecimal(20), BigDecimal(30), BigDecimal(40), BigDecimal(50)] }
712
700
 
713
701
  before do
714
702
  plugin.register
@@ -1516,6 +1504,49 @@ describe LogStash::Inputs::Jdbc do
1516
1504
  end
1517
1505
  end
1518
1506
 
1507
+ context "when retrieving records with ambiguous timestamps" do
1508
+
1509
+ let(:settings) do
1510
+ {
1511
+ "statement" => "SELECT * from types_table",
1512
+ "jdbc_default_timezone" => jdbc_default_timezone
1513
+ }
1514
+ end
1515
+
1516
+ before(:each) do
1517
+ db << "INSERT INTO types_table (num, string, started_at, custom_time, ranking) VALUES (1, 'A test', '1999-12-31', '2021-11-07 01:23:45', 95.67)"
1518
+ plugin.register
1519
+ end
1520
+
1521
+ context "when initialized with a preference for DST being enabled" do
1522
+ let(:jdbc_default_timezone) { 'America/Chicago[dst_enabled_on_overlap:true]' }
1523
+
1524
+ it 'treats the timestamp column as if DST was enabled' do
1525
+ plugin.run(queue)
1526
+ event = queue.pop
1527
+ expect(event.get("custom_time")).to be_a_logstash_timestamp_equivalent_to("2021-11-07T06:23:45Z")
1528
+ end
1529
+ end
1530
+ context "when initialized with a preference for DST being disabled" do
1531
+ let(:jdbc_default_timezone) { 'America/Chicago[dst_enabled_on_overlap:false]' }
1532
+
1533
+ it 'treats the timestamp column as if DST was disabled' do
1534
+ plugin.run(queue)
1535
+ event = queue.pop
1536
+ expect(event.get("custom_time")).to be_a_logstash_timestamp_equivalent_to("2021-11-07T07:23:45Z")
1537
+ end
1538
+ end
1539
+ context "when initialized without a preference for DST being enabled or disabled" do
1540
+ before(:each) { allow(plugin.logger).to receive(:warn) }
1541
+ let(:jdbc_default_timezone) { 'America/Chicago' }
1542
+
1543
+ it 'the error results in helpful log warning' do
1544
+ plugin.run(queue)
1545
+ expect(plugin.logger).to have_received(:warn).with(a_string_including("Exception when executing JDBC query"), a_hash_including(:message => a_string_including("2021-11-07 01:23:45 is an ambiguous local time")))
1546
+ end
1547
+ end
1548
+ end
1549
+
1519
1550
  context "when an unreadable jdbc_driver_path entry is present" do
1520
1551
  let(:driver_jar_path) do
1521
1552
  jar_file = $CLASSPATH.find { |name| name.index(Jdbc::Derby.driver_jar) }
@@ -1717,7 +1748,7 @@ describe LogStash::Inputs::Jdbc do
1717
1748
  let(:jdbc_driver_class) { "org.apache.NonExistentDriver" }
1718
1749
  it "raises a loading error" do
1719
1750
  expect { plugin.send(:load_driver) }.to raise_error LogStash::PluginLoadingError,
1720
- /java.lang.ClassNotFoundException: org.apache.NonExistentDriver/
1751
+ /ClassNotFoundException: org.apache.NonExistentDriver/
1721
1752
  end
1722
1753
  end
1723
1754
  end
@@ -0,0 +1,68 @@
1
+ # encoding: utf-8
2
+ require "logstash/devutils/rspec/spec_helper"
3
+ require "logstash/plugin_mixins/jdbc/timezone_proxy"
4
+
5
+ describe LogStash::PluginMixins::Jdbc::TimezoneProxy do
6
+ subject(:timezone) { described_class.load(timezone_spec) }
7
+
8
+ context 'when handling a daylight-savings ambiguous time' do
9
+ context 'without extensions' do
10
+ let(:timezone_spec) { 'America/Los_Angeles[]' }
11
+ it 'raises an AmbiguousTime error' do
12
+ expect { timezone.local_time(2021,11,7,1,17) }.to raise_error(::TZInfo::AmbiguousTime)
13
+ end
14
+ end
15
+ context 'with extension `dst_enabled_on_overlap:true`' do
16
+ let(:timezone_spec) { 'America/Los_Angeles[dst_enabled_on_overlap:true]' }
17
+ it 'resolves as if DST were enabled' do
18
+ timestamp = timezone.local_time(2021,11,7,1,17)
19
+ aggregate_failures do
20
+ expect(timestamp.dst?).to be true
21
+ expect(timestamp.zone).to eq('PDT') # Pacific Daylight Time
22
+ expect(timestamp.getutc).to eq(Time.utc(2021,11,7,8,17))
23
+ expect(timestamp.utc_offset).to eq( -7 * 3600 )
24
+ end
25
+ end
26
+ end
27
+ context 'with extension `dst_enabled_on_overlap:false`' do
28
+ let(:timezone_spec) { 'America/Los_Angeles[dst_enabled_on_overlap:false]' }
29
+ it 'resolves as if DST were disabled' do
30
+ timestamp = timezone.local_time(2021,11,7,1,17)
31
+ aggregate_failures do
32
+ expect(timestamp.dst?).to be false
33
+ expect(timestamp.zone).to eq('PST') # Pacific Standard Time
34
+ expect(timestamp.getutc).to eq(Time.utc(2021,11,7,9,17))
35
+ expect(timestamp.utc_offset).to eq( -8 * 3600 )
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ context '#load' do
42
+ context 'when spec is a normal timezone instance' do
43
+ let(:timezone_spec) { ::TZInfo::Timezone.get('America/Los_Angeles') }
44
+ it 'returns that instance' do
45
+ expect(timezone).to be(timezone_spec)
46
+ end
47
+ end
48
+ context 'when spec is a valid unextended timezone spec' do
49
+ let(:timezone_spec) { 'America/Los_Angeles' }
50
+ it 'returns the canonical timezone' do
51
+ expect(timezone).to eq(::TZInfo::Timezone.get('America/Los_Angeles'))
52
+ end
53
+ end
54
+ context 'when spec is an invalid timezone spec' do
55
+ let(:timezone_spec) { 'NotAValidTimezoneIdentifier' }
56
+
57
+ it 'propagates the TZInfo exception' do
58
+ expect { timezone }.to raise_exception(::TZInfo::InvalidTimezoneIdentifier)
59
+ end
60
+ end
61
+ context 'with invalid extension' do
62
+ let(:timezone_spec) { 'America/Los_Angeles[dst_enabled_on_overlap:false;nope:wrong]' }
63
+ it 'raises an exception with a helpful message' do
64
+ expect { timezone }.to raise_exception(ArgumentError, a_string_including("Invalid timezone extension `nope:wrong`"))
65
+ end
66
+ end
67
+ end
68
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: logstash-integration-jdbc
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.2.6
4
+ version: 5.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Elastic
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-05-19 00:00:00.000000000 Z
11
+ date: 2022-10-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  requirement: !ruby/object:Gem::Requirement
@@ -131,38 +131,38 @@ dependencies:
131
131
  - !ruby/object:Gem::Dependency
132
132
  requirement: !ruby/object:Gem::Requirement
133
133
  requirements:
134
- - - ">="
134
+ - - "~>"
135
135
  - !ruby/object:Gem::Version
136
- version: '0'
137
- name: rufus-scheduler
136
+ version: '1.3'
137
+ name: logstash-mixin-ecs_compatibility_support
138
138
  prerelease: false
139
139
  type: :runtime
140
140
  version_requirements: !ruby/object:Gem::Requirement
141
141
  requirements:
142
- - - ">="
142
+ - - "~>"
143
143
  - !ruby/object:Gem::Version
144
- version: '0'
144
+ version: '1.3'
145
145
  - !ruby/object:Gem::Dependency
146
146
  requirement: !ruby/object:Gem::Requirement
147
147
  requirements:
148
148
  - - "~>"
149
149
  - !ruby/object:Gem::Version
150
- version: '1.3'
151
- name: logstash-mixin-ecs_compatibility_support
150
+ version: '1.0'
151
+ name: logstash-mixin-validator_support
152
152
  prerelease: false
153
153
  type: :runtime
154
154
  version_requirements: !ruby/object:Gem::Requirement
155
155
  requirements:
156
156
  - - "~>"
157
157
  - !ruby/object:Gem::Version
158
- version: '1.3'
158
+ version: '1.0'
159
159
  - !ruby/object:Gem::Dependency
160
160
  requirement: !ruby/object:Gem::Requirement
161
161
  requirements:
162
162
  - - "~>"
163
163
  - !ruby/object:Gem::Version
164
164
  version: '1.0'
165
- name: logstash-mixin-validator_support
165
+ name: logstash-mixin-event_support
166
166
  prerelease: false
167
167
  type: :runtime
168
168
  version_requirements: !ruby/object:Gem::Requirement
@@ -176,7 +176,7 @@ dependencies:
176
176
  - - "~>"
177
177
  - !ruby/object:Gem::Version
178
178
  version: '1.0'
179
- name: logstash-mixin-event_support
179
+ name: logstash-mixin-scheduler
180
180
  prerelease: false
181
181
  type: :runtime
182
182
  version_requirements: !ruby/object:Gem::Requirement
@@ -277,8 +277,8 @@ files:
277
277
  - lib/logstash/inputs/tzinfo_jruby_patch.rb
278
278
  - lib/logstash/plugin_mixins/jdbc/common.rb
279
279
  - lib/logstash/plugin_mixins/jdbc/jdbc.rb
280
- - lib/logstash/plugin_mixins/jdbc/scheduler.rb
281
280
  - lib/logstash/plugin_mixins/jdbc/statement_handler.rb
281
+ - lib/logstash/plugin_mixins/jdbc/timezone_proxy.rb
282
282
  - lib/logstash/plugin_mixins/jdbc/value_tracking.rb
283
283
  - lib/logstash/plugin_mixins/jdbc_streaming.rb
284
284
  - lib/logstash/plugin_mixins/jdbc_streaming/cache_payload.rb
@@ -306,7 +306,7 @@ files:
306
306
  - spec/helpers/derbyrun.jar
307
307
  - spec/inputs/integration/integ_spec.rb
308
308
  - spec/inputs/jdbc_spec.rb
309
- - spec/plugin_mixins/jdbc/scheduler_spec.rb
309
+ - spec/plugin_mixins/jdbc/timezone_proxy_spec.rb
310
310
  - spec/plugin_mixins/jdbc_streaming/parameter_handler_spec.rb
311
311
  - vendor/jar-dependencies/org/apache/derby/derby/10.14.1.0/derby-10.14.1.0.jar
312
312
  - vendor/jar-dependencies/org/apache/derby/derbyclient/10.14.1.0/derbyclient-10.14.1.0.jar
@@ -359,5 +359,5 @@ test_files:
359
359
  - spec/helpers/derbyrun.jar
360
360
  - spec/inputs/integration/integ_spec.rb
361
361
  - spec/inputs/jdbc_spec.rb
362
- - spec/plugin_mixins/jdbc/scheduler_spec.rb
362
+ - spec/plugin_mixins/jdbc/timezone_proxy_spec.rb
363
363
  - spec/plugin_mixins/jdbc_streaming/parameter_handler_spec.rb
@@ -1,175 +0,0 @@
1
- require 'rufus/scheduler'
2
-
3
- require 'logstash/util/loggable'
4
-
5
- module LogStash module PluginMixins module Jdbc
6
- class Scheduler < Rufus::Scheduler
7
-
8
- include LogStash::Util::Loggable
9
-
10
- # Rufus::Scheduler >= 3.4 moved the Time impl into a gem EoTime = ::EtOrbi::EoTime`
11
- # Rufus::Scheduler 3.1 - 3.3 using it's own Time impl `Rufus::Scheduler::ZoTime`
12
- TimeImpl = defined?(Rufus::Scheduler::EoTime) ? Rufus::Scheduler::EoTime :
13
- (defined?(Rufus::Scheduler::ZoTime) ? Rufus::Scheduler::ZoTime : ::Time)
14
-
15
- # @param cron [String] cron-line
16
- # @param opts [Hash] scheduler options
17
- # @return scheduler instance
18
- def self.start_cron_scheduler(cron, opts = {}, &block)
19
- unless block_given?
20
- raise ArgumentError, 'missing (cron scheduler) block - worker task to execute'
21
- end
22
- scheduler = new_scheduler(opts)
23
- scheduler.schedule_cron(cron, &block)
24
- scheduler
25
- end
26
-
27
- # @param opts [Hash] scheduler options
28
- # @return scheduler instance
29
- def self.new_scheduler(opts)
30
- unless opts.key?(:thread_name)
31
- raise ArgumentError, 'thread_name: option is required to be able to distinguish multiple scheduler threads'
32
- end
33
- opts[:max_work_threads] ||= 1
34
- # amount the scheduler thread sleeps between checking whether jobs
35
- # should trigger, default is 0.3 which is a bit too often ...
36
- # in theory the cron expression '* * * * * *' supports running jobs
37
- # every second but this is very rare, we could potentially go higher
38
- opts[:frequency] ||= 1.0
39
-
40
- new(opts)
41
- end
42
-
43
- # @overload
44
- def timeout_jobs
45
- # Rufus relies on `Thread.list` which is a blocking operation and with many schedulers
46
- # (and threads) within LS will have a negative impact on performance as scheduler
47
- # threads will end up waiting to obtain the `Thread.list` lock.
48
- #
49
- # However, this isn't necessary we can easily detect whether there are any jobs
50
- # that might need to timeout: only when `@opts[:timeout]` is set causes worker thread(s)
51
- # to have a `Thread.current[:rufus_scheduler_timeout]` that is not nil
52
- return unless @opts[:timeout]
53
- super
54
- end
55
-
56
- # @overload
57
- def work_threads(query = :all)
58
- if query == :__all_no_cache__ # special case from JobDecorator#start_work_thread
59
- @_work_threads = nil # when a new worker thread is being added reset
60
- return super(:all)
61
- end
62
-
63
- # Gets executed every time a job is triggered, we're going to cache the
64
- # worker threads for this scheduler (to avoid `Thread.list`) - they only
65
- # change when a new thread is being started from #start_work_thread ...
66
- work_threads = @_work_threads
67
- if work_threads.nil?
68
- work_threads = threads.select { |t| t[:rufus_scheduler_work_thread] }
69
- @_work_threads = work_threads
70
- end
71
-
72
- case query
73
- when :active then work_threads.select { |t| t[:rufus_scheduler_job] }
74
- when :vacant then work_threads.reject { |t| t[:rufus_scheduler_job] }
75
- else work_threads
76
- end
77
- end
78
-
79
- # @overload
80
- def on_error(job, err)
81
- details = { exception: err.class, message: err.message, backtrace: err.backtrace }
82
- details[:cause] = err.cause if err.cause
83
-
84
- details[:now] = debug_format_time(TimeImpl.now)
85
- details[:last_time] = (debug_format_time(job.last_time) rescue nil)
86
- details[:next_time] = (debug_format_time(job.next_time) rescue nil)
87
- details[:job] = job
88
-
89
- details[:opts] = @opts
90
- details[:started_at] = started_at
91
- details[:thread] = thread.inspect
92
- details[:jobs_size] = @jobs.size
93
- details[:work_threads_size] = work_threads.size
94
- details[:work_queue_size] = work_queue.size
95
-
96
- logger.error("Scheduler intercepted an error:", details)
97
-
98
- rescue => e
99
- logger.error("Scheduler failed in #on_error #{e.inspect}")
100
- end
101
-
102
- def debug_format_time(time)
103
- # EtOrbi::EoTime used by (newer) Rufus::Scheduler has to_debug_s https://git.io/JyiPj
104
- time.respond_to?(:to_debug_s) ? time.to_debug_s : time.strftime("%Y-%m-%dT%H:%M:%S.%L")
105
- end
106
- private :debug_format_time
107
-
108
- # @private helper used by JobDecorator
109
- def work_thread_name_prefix
110
- ( @opts[:thread_name] || "#{@thread_key}_scheduler" ) + '_worker-'
111
- end
112
-
113
- protected
114
-
115
- # @overload
116
- def start
117
- ret = super() # @thread[:name] = @opts[:thread_name] || "#{@thread_key}_scheduler"
118
-
119
- # at least set thread.name for easier thread dump analysis
120
- if @thread.is_a?(Thread) && @thread.respond_to?(:name=)
121
- @thread.name = @thread[:name] if @thread[:name]
122
- end
123
-
124
- ret
125
- end
126
-
127
- # @overload
128
- def do_schedule(job_type, t, callable, opts, return_job_instance, block)
129
- job_or_id = super
130
-
131
- job_or_id.extend JobDecorator if return_job_instance
132
-
133
- job_or_id
134
- end
135
-
136
- module JobDecorator
137
-
138
- def start_work_thread
139
- prev_thread_count = @scheduler.work_threads.size
140
-
141
- ret = super() # does not return Thread instance in 3.0
142
-
143
- work_threads = @scheduler.work_threads(:__all_no_cache__)
144
- while prev_thread_count == work_threads.size # very unlikely
145
- Thread.pass
146
- work_threads = @scheduler.work_threads(:__all_no_cache__)
147
- end
148
-
149
- work_thread_name_prefix = @scheduler.work_thread_name_prefix
150
-
151
- work_threads.sort! do |t1, t2|
152
- if t1[:name].nil?
153
- t2[:name].nil? ? 0 : +1 # nils at the end
154
- elsif t2[:name].nil?
155
- t1[:name].nil? ? 0 : -1
156
- else
157
- t1[:name] <=> t2[:name]
158
- end
159
- end
160
-
161
- work_threads.each_with_index do |thread, i|
162
- unless thread[:name]
163
- thread[:name] = "#{work_thread_name_prefix}#{sprintf('%02i', i)}"
164
- thread.name = thread[:name] if thread.respond_to?(:name=)
165
- # e.g. "[oracle]<jdbc_scheduler_worker-00"
166
- end
167
- end
168
-
169
- ret
170
- end
171
-
172
- end
173
-
174
- end
175
- end end end
@@ -1,78 +0,0 @@
1
- # encoding: utf-8
2
- require "logstash/devutils/rspec/spec_helper"
3
- require "logstash/plugin_mixins/jdbc/scheduler"
4
-
5
- describe LogStash::PluginMixins::Jdbc::Scheduler do
6
-
7
- let(:thread_name) { '[test]<jdbc_scheduler' }
8
-
9
- let(:opts) do
10
- { :max_work_threads => 2, :thread_name => thread_name }
11
- end
12
-
13
- subject(:scheduler) { LogStash::PluginMixins::Jdbc::Scheduler.new(opts) }
14
-
15
- after { scheduler.stop(:wait) }
16
-
17
- it "sets scheduler thread name" do
18
- expect( scheduler.thread.name ).to include thread_name
19
- end
20
-
21
- context 'cron schedule' do
22
-
23
- before do
24
- scheduler.schedule_cron('* * * * * *') { sleep 1.25 } # every second
25
- end
26
-
27
- it "sets worker thread names" do
28
- sleep 3.0
29
- threads = scheduler.work_threads
30
- threads.sort! { |t1, t2| (t1.name || '') <=> (t2.name || '') }
31
-
32
- expect( threads.size ).to eql 2
33
- expect( threads.first.name ).to eql "#{thread_name}_worker-00"
34
- expect( threads.last.name ).to eql "#{thread_name}_worker-01"
35
- end
36
-
37
- end
38
-
39
- context 'every 1s' do
40
-
41
- before do
42
- scheduler.schedule_in('1s') { raise 'TEST' } # every second
43
- end
44
-
45
- it "logs errors handled" do
46
- expect( scheduler.logger ).to receive(:error).with /Scheduler intercepted an error/, hash_including(:message => 'TEST')
47
- sleep 1.5
48
- end
49
-
50
- end
51
-
52
- context 'work threads' do
53
-
54
- let(:opts) { super().merge :max_work_threads => 3 }
55
-
56
- let(:counter) { java.util.concurrent.atomic.AtomicLong.new(0) }
57
-
58
- before do
59
- scheduler.schedule_cron('* * * * * *') { counter.increment_and_get; sleep 3.25 } # every second
60
- end
61
-
62
- it "are working" do
63
- sleep(0.05) while counter.get == 0
64
- expect( scheduler.work_threads.size ).to eql 1
65
- sleep(0.05) while counter.get == 1
66
- expect( scheduler.work_threads.size ).to eql 2
67
- sleep(0.05) while counter.get == 2
68
- expect( scheduler.work_threads.size ).to eql 3
69
-
70
- sleep 1.25
71
- expect( scheduler.work_threads.size ).to eql 3
72
- sleep 1.25
73
- expect( scheduler.work_threads.size ).to eql 3
74
- end
75
-
76
- end
77
-
78
- end