diff --git a/.ruby-version b/.ruby-version index cd57a8b95..54b50ff27 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1,2 @@ 2.1.5 + diff --git a/.travis.yml b/.travis.yml index 306604071..02c8f29c4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,9 +2,8 @@ language: ruby matrix: include: - rvm: 1.9.3 - gemfile: Gemfile-ruby1.9 + gemfile: Gemfile script: "bundle exec rspec spec && bundle exec rake pact:verify && bundle exec cucumber features" - env: BUNDLE_GEMFILE=Gemfile-ruby1.9 - rvm: '2.0' gemfile: Gemfile script: "bundle exec rspec spec && bundle exec rake pact:verify && bundle exec cucumber features" diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b12083fb..cec4ba076 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,27 @@ ## Flapjack Changelog +# 1.2.0 - 2014-11-07 +- Bug: multi blocks for safe redis connection pool usage #694 (@ali-graham) +- Bug: data migration to work around previous notification rule bug #699 (@ali-graham) + +# 1.2.0rc2 - 2014-10-17 +- Feature: Cache entity and check name <-> id lookups #682 (@ali-graham) +- Chore: Add last_change field to status_reports in the API (closes: #678) #679 (@Hobbsee) +- Chore: Move archive cache responsibility to mirror source. #683 (@ali-graham) +- Bug: jsonapi: GET /entities is non performant #674 (@ali-graham) + +# 1.2.0rc1 - 2014-10-08 +- Feature: Allow updating an entities name via PATCH /entities/id[,id...] #628 (@ali-graham) +- Feature: Pact specs for flapjack-diner compatability testing #663 (@ali-graham) +- Feature: more api check methods #644 (@ali-graham) +- Chore: optimise tag code #654 (@ali-graham) +- Chore: fix #653, overuse of Redis KEYS in mirror #661 (@ali-graham) +- Bug: Entities should return linked checks with list of check "ids" in API #648 (@ali-graham) +- Bug: 500 error getting all checks from API #641 (@ali-graham) +- Bug: Pager Duty credentials get throws error #657 (@ali-graham) +- Bug: Flapjack crashes when email alert is triggered #656 (@ali-graham) +- Bug: Fix /edit_contacts media saving after adding contacts #651 (@ali-graham) + # 1.1.0 - 2014-09-10 - Feature: Add autorefresh for the web GUI (Closes: #494) #607 (@Hobbsee) - Feature: twilio sms sending #633 (@jessereynolds) diff --git a/Dockerfile b/Dockerfile index 1b73f292f..8e159e191 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,7 +5,7 @@ RUN apt-get install -y gnupg RUN gpg --keyserver keys.gnupg.net --recv-keys 803709B6 RUN gpg -a --export 803709B6 | apt-key add - -RUN echo "deb http://packages.flapjack.io/deb/1.0 trusty main" | tee /etc/apt/sources.list.d/flapjack.list +RUN echo "deb http://packages.flapjack.io/deb/v1 trusty main" | tee /etc/apt/sources.list.d/flapjack.list RUN apt-get update RUN apt-cache policy flapjack RUN apt-get install -y flapjack diff --git a/Gemfile b/Gemfile index 91ee0167f..a96ed2b6c 100644 --- a/Gemfile +++ b/Gemfile @@ -10,17 +10,11 @@ end group :test do gem 'rspec' - gem 'cucumber' + gem 'cucumber', '>= 2.0.0.beta.3' gem 'delorean' gem 'rack-test' gem 'webmock' gem 'pact' gem 'fuubar' gem 'simplecov', :require => false - - # # Not compiling at the moment - # if RUBY_VERSION.split('.')[0] == '1' && RUBY_VERSION.split('.')[1] == '9' - # gem 'debugger-ruby_core_source', :github => 'moneill/debugger-ruby_core_source', :branch => '1.9.3-p550_headers' # required for perftools.rb - # gem 'perftools.rb' - # end end diff --git a/Gemfile-ruby1.9 b/Gemfile-ruby1.9 deleted file mode 120000 index 6ab79009c..000000000 --- a/Gemfile-ruby1.9 +++ /dev/null @@ -1 +0,0 @@ -Gemfile \ No newline at end of file diff --git a/Gemfile-ruby1.9.lock b/Gemfile-ruby1.9.lock deleted file mode 100644 index b4e57502b..000000000 --- a/Gemfile-ruby1.9.lock +++ /dev/null @@ -1,167 +0,0 @@ -GIT - remote: git://github.com/flapjack/sandstorm.git - revision: 45ea4a66905d9df42ac98025f3503a158ce4f632 - branch: master - specs: - sandstorm (0.1.0) - activemodel - -PATH - remote: . - specs: - flapjack (1.1.0) - activesupport - chronic - chronic_duration - dante - gli (= 2.12.0) - hiredis - ice_cube - mail - oj (>= 2.9.0) - rbtrace - redis (>= 3.0.7) - sinatra - terminal-table - toml - tzinfo - tzinfo-data - xmpp4r (>= 0.5.5) - -GEM - remote: https://rubygems.org/ - specs: - activemodel (4.1.7) - activesupport (= 4.1.7) - builder (~> 3.1) - activesupport (4.1.7) - i18n (~> 0.6, >= 0.6.9) - json (~> 1.7, >= 1.7.7) - minitest (~> 5.1) - thread_safe (~> 0.1) - tzinfo (~> 1.1) - addressable (2.3.6) - awesome_print (1.2.0) - blankslate (2.1.2.4) - builder (3.2.2) - chronic (0.10.2) - chronic_duration (0.10.6) - numerizer (~> 0.1.1) - crack (0.4.2) - safe_yaml (~> 1.0.0) - cucumber (1.3.17) - builder (>= 2.1.2) - diff-lcs (>= 1.1.3) - gherkin (~> 2.12) - multi_json (>= 1.7.5, < 2.0) - multi_test (>= 0.1.1) - dante (0.2.0) - delorean (2.1.0) - chronic - diff-lcs (1.2.5) - docile (1.1.5) - ffi (1.9.6) - find_a_port (1.0.1) - fuubar (2.0.0) - rspec (~> 3.0) - ruby-progressbar (~> 1.4) - gherkin (2.12.2) - multi_json (~> 1.3) - gli (2.12.0) - hiredis (0.5.2) - i18n (0.6.11) - ice_cube (0.12.1) - json (1.8.1) - mail (2.6.3) - mime-types (>= 1.16, < 3) - mime-types (2.4.3) - minitest (5.4.3) - msgpack (0.5.9) - multi_json (1.10.1) - multi_test (0.1.1) - numerizer (0.1.1) - oj (2.11.1) - pact (1.3.3) - awesome_print (~> 1.1) - find_a_port (~> 1.0.1) - json - rack-test (~> 0.6.2) - randexp (~> 0.1.7) - rspec (>= 2.14) - term-ansicolor (~> 1.0) - thor - webrick - parslet (1.5.0) - blankslate (~> 2.0) - rack (1.5.2) - rack-protection (1.5.3) - rack - rack-test (0.6.2) - rack (>= 1.0) - rake (10.3.2) - randexp (0.1.7) - rbtrace (0.4.5) - ffi (>= 1.0.6) - msgpack (>= 0.4.3) - trollop (>= 1.16.2) - redis (3.1.0) - rspec (3.1.0) - rspec-core (~> 3.1.0) - rspec-expectations (~> 3.1.0) - rspec-mocks (~> 3.1.0) - rspec-core (3.1.3) - rspec-support (~> 3.1.0) - rspec-expectations (3.1.1) - diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.1.0) - rspec-mocks (3.1.0) - rspec-support (~> 3.1.0) - rspec-support (3.1.0) - ruby-prof (0.15.1) - ruby-progressbar (1.5.1) - safe_yaml (1.0.4) - simplecov (0.9.0) - docile (~> 1.1.0) - multi_json - simplecov-html (~> 0.8.0) - simplecov-html (0.8.0) - sinatra (1.4.5) - rack (~> 1.4) - rack-protection (~> 1.4) - tilt (~> 1.3, >= 1.3.4) - term-ansicolor (1.3.0) - tins (~> 1.0) - terminal-table (1.4.5) - thor (0.19.1) - thread_safe (0.3.4) - tilt (1.4.1) - tins (1.3.3) - toml (0.1.2) - parslet (~> 1.5.0) - trollop (2.0) - tzinfo (1.2.2) - thread_safe (~> 0.1) - tzinfo-data (1.2014.10) - tzinfo (>= 1.0.0) - webmock (1.20.4) - addressable (>= 2.3.6) - crack (>= 0.3.2) - webrick (1.3.1) - xmpp4r (0.5.6) - -PLATFORMS - ruby - -DEPENDENCIES - cucumber - delorean - flapjack! - fuubar - pact - rack-test - rake - rspec - ruby-prof - sandstorm! - simplecov - webmock diff --git a/Gemfile.lock b/Gemfile.lock index 5e06ec924..024c9c473 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -9,7 +9,7 @@ GIT PATH remote: . specs: - flapjack (1.1.0) + flapjack (2.0.0a1) activesupport chronic chronic_duration @@ -49,12 +49,15 @@ GEM numerizer (~> 0.1.1) crack (0.4.2) safe_yaml (~> 1.0.0) - cucumber (1.3.17) + cucumber (2.0.0.beta.3) builder (>= 2.1.2) + cucumber-core (~> 1.0.0.beta.3) diff-lcs (>= 1.1.3) gherkin (~> 2.12) multi_json (>= 1.7.5, < 2.0) multi_test (>= 0.1.1) + cucumber-core (1.0.0.beta.3) + gherkin (~> 2.12.0) dante (0.2.0) delorean (2.1.0) chronic @@ -153,7 +156,7 @@ PLATFORMS ruby DEPENDENCIES - cucumber + cucumber (>= 2.0.0.beta.3) delorean flapjack! fuubar diff --git a/features/steps/flapjack-netsaint-parser_steps.rb b/features/steps/flapjack-netsaint-parser_steps.rb index 6e1e059dd..6dd1ec984 100644 --- a/features/steps/flapjack-netsaint-parser_steps.rb +++ b/features/steps/flapjack-netsaint-parser_steps.rb @@ -14,7 +14,7 @@ Then /^I should see a valid JSON output$/ do expect { - @data = Oj.load(@output) + @data = Flapjack.load_json(@output) }.not_to raise_error end diff --git a/features/support/env.rb b/features/support/env.rb index cf1474642..2df84646e 100755 --- a/features/support/env.rb +++ b/features/support/env.rb @@ -193,7 +193,7 @@ def redis_peek(queue, klass, start = 0, count = nil) 'tmp/cucumber_cli/flapjack-populator-entities.json', ].each do |file| next unless File.exists?(file) - # File.unlink(file) + File.unlink(file) end end diff --git a/lib/flapjack/cli/maintenance.rb b/lib/flapjack/cli/maintenance.rb index 2e4c98586..af54e3cff 100644 --- a/lib/flapjack/cli/maintenance.rb +++ b/lib/flapjack/cli/maintenance.rb @@ -282,23 +282,23 @@ def self.extract_time_range(time_period_in_words, base_time) def common_arguments(cmd_type, gli_cmd) if [:show, :delete, :create].include?(cmd_type) - gli_cmd.flag [:c, 'check'], + gli_cmd.flag ['check', :c], :desc => 'The check for the maintenance window to occur on. This can ' + 'be a string, or a Ruby regex of the form \'http*\' or \'[[:lower:]]\'', :required => :create.eql?(cmd_type) - gli_cmd.flag [:r, 'reason'], + gli_cmd.flag ['reason', :r], :desc => 'The reason for the maintenance window to occur. This can ' + 'be a string, or a Ruby regex of the form \'Downtime for *\' or ' + '\'[[:lower:]]\'' - gli_cmd.flag [:s, 'start', 'started', 'starting'], + gli_cmd.flag ['start', 'started', 'starting', :s], :desc => 'The start time for the maintenance window. This should ' + 'be prefixed with "more than", "less than", "on", "before", ' + 'or "after", or of the form "between T1 and T2"', :must_match => /^(?:more than|less than|on|before|after|between)\s+.+$/ - gli_cmd.flag [:d, 'duration'], + gli_cmd.flag ['duration', :d], :desc => 'The total duration of the maintenance window. This should ' + 'be prefixed with "more than", "less than", or "equal to", ' + 'or of the form "between M and N hours". This should be an ' + @@ -307,19 +307,19 @@ def common_arguments(cmd_type, gli_cmd) end if [:show, :delete].include?(cmd_type) - gli_cmd.flag [:f, 'finish', 'finished', 'finishing', 'remain', 'remained', 'remaining', 'end'], + gli_cmd.flag ['finish', 'finished', 'finishing', 'remain', 'remained', 'remaining', 'end', :f], :desc => 'The finishing time for the maintenance window. This should ' + 'prefixed with "more than", "less than", "on", "before", or ' + '"after", or of the form "between T1 and T2"' , :must_match => /^(?:more than|less than|on|before|after|between)\s+.+$/ - gli_cmd.flag [:st, 'state'], + gli_cmd.flag ['state', :st], :desc => 'The state that the check is currently in', :must_match => %w(ok warning critical unknown) end if [:show, :delete, :create].include?(cmd_type) - gli_cmd.flag [:t, 'type'], + gli_cmd.flag ['type', :t], :desc => 'The type of maintenance scheduled', :required => true, :default_value => 'scheduled', diff --git a/lib/flapjack/cli/receiver.rb b/lib/flapjack/cli/receiver.rb index 9757bb5a0..1c41fcfab 100644 --- a/lib/flapjack/cli/receiver.rb +++ b/lib/flapjack/cli/receiver.rb @@ -22,11 +22,15 @@ def initialize(global_options, options) @config_env = @config.all if @config_env.nil? || @config_env.empty? - exit_now! "No config data found in '#{global_options[:config]}'" + unless 'mirror'.eql?(@options[:type]) + exit_now! "No config data found in '#{global_options[:config]}'" + end end - Flapjack::RedisProxy.config = @config.for_redis - Sandstorm.redis = Flapjack.redis + unless 'mirror'.eql?(@options[:type]) + Flapjack::RedisProxy.config = @config.for_redis + Sandstorm.redis = Flapjack.redis + end @config_runner = @config_env["#{@options[:type]}-receiver"] || {} @@ -109,9 +113,17 @@ def json end def mirror + if (@options[:dest].nil? || @options[:dest].strip.empty?) && + @redis_options.nil? + + exit_now! "No destination redis URL passed, and none configured" + end + mirror_receive(:source => @options[:source], - :all => @options[:all], :follow => @options[:follow], - :last => @options[:last], :time => @options[:time]) + :dest => @options[:dest] || @redis_options, + :include => @options[:include], :all => @options[:all], + :follow => @options[:follow], :last => @options[:last], + :time => @options[:time]) end private @@ -328,16 +340,38 @@ def json_feeder(opts = {}) puts "Done." end - def mirror_receive(opts) unless opts[:follow] || opts[:all] exit_now! "one or both of --follow or --all is required" end - source_redis = Redis.new(:url => opts[:source]) + include_re = nil + unless opts[:include].nil? || opts[:include].strip.empty? + begin + include_re = Regexp.new(opts[:include].strip) + rescue RegexpError + exit_now! "could not parse include Regexp: #{opts[:include].strip}" + end + end + + source_addr = opts[:source] + source_redis = Redis.new(:url => source_addr, :driver => :hiredis) + + dest_addr = opts[:dest] + dest_redis = case dest_addr + when Hash + Redis.new(dest_addr.merge(:driver => :hiredis)) + when String + Redis.new(:url => dest_addr, :driver => :hiredis) + else + exit_now! "could not understand destination Redis config" + end + + Flapjack::RedisProxy.config = dest_redis + Sandstorm.redis = Flapjack.redis - archives = mirror_get_archive_keys_stats(source_redis) - raise "found no archives!" unless archives && archives.length > 0 + archives = mirror_get_archive_keys_stats(:source => source_redis) + raise "found no archives!" if archives.empty? puts "found archives: #{archives.inspect}" @@ -350,53 +384,62 @@ def mirror_receive(opts) events_sent = 0 case when opts[:all] - archive_key = archives[0][:name] + archive_idx = 0 cursor = -1 when opts[:last], opts[:time] raise "Sorry, unimplemented" else # wait for the next event to be archived, so point the cursor at a non-existant # slot in the list, the one before the 0'th - archive_key = archives[-1][:name] + archive_idx = archives.size - 1 cursor = -1 - archives[-1][:size] end + archive_key = archives[archive_idx][:name] puts archive_key loop do - new_archive_key = false - # something to read at cursor? - event = source_redis.lindex(archive_key, cursor) - if event - Flapjack::Data::Event.push('events', event) - events_sent += 1 - print "#{events_sent} " if events_sent % 1000 == 0 + event_json = source_redis.lindex(archive_key, cursor) + if event_json + event = Flapjack::Data::Event.parse_and_validate(event_json) + if !event.nil? && (include_re.nil? || + (include_re === "#{event['entity']}:#{event['check']}")) + + Flapjack::Data::Event.add(event) + events_sent += 1 + print "#{events_sent} " if events_sent % 1000 == 0 + end cursor -= 1 + next + end + + archives = mirror_get_archive_keys_stats(:source => source_redis).select {|a| + a[:size] > 0 + } + + if archives.empty? + sleep 1 + next + end + + archive_idx = archives.index {|a| a[:name] == archive_key } + archive_idx = archive_idx.nil? ? 0 : (archive_idx + 1) + if archives[archive_idx] + archive_key = archives[archive_idx][:name] + puts archive_key + cursor = -1 else - puts "\narchive key: #{archive_key}, cursor: #{cursor}" - # do we need to look at the next archive bucket? - archives = mirror_get_archive_keys_stats(source_redis) - i = archives.index {|a| a[:name] == archive_key } - if archives[i][:size] = (cursor.abs + 1) - if archives[i + 1] - archive_key = archives[i + 1][:name] - puts archive_key - cursor = -1 - new_archive_key = true - else - return unless opts[:follow] - end - end - sleep 1 unless new_archive_key + break unless opts[:follow] + sleep 1 end end end - def mirror_get_archive_keys_stats(source_redis) - source_redis.keys("events_archive:*").sort.map {|a| - { :name => a, - :size => source_redis.llen(a) } - } + def mirror_get_archive_keys_stats(opts = {}) + source_redis = opts[:source] + source_redis.smembers("known_events_archive_keys").sort.collect do |eak| + {:name => eak, :size => source_redis.llen(eak)} + end end end diff --git a/lib/flapjack/data/notification.rb b/lib/flapjack/data/notification.rb index c71550c73..922576846 100644 --- a/lib/flapjack/data/notification.rb +++ b/lib/flapjack/data/notification.rb @@ -70,173 +70,170 @@ def state_or_ack end end + def alerts_for(route_ids_by_contact_id, opts = {}) + logger = opts[:logger] - def alerts_for(route_ids_by_contact_id, opts = {}) - logger = opts[:logger] + transports = opts[:transports] - transports = opts[:transports] + timestamp = opts[:timestamp] + default_timezone = opts[:default_timezone] + safe_state_or_ack = self.state_or_ack - timestamp = opts[:timestamp] - default_timezone = opts[:default_timezone] - safe_state_or_ack = self.state_or_ack + notification_state = self.state ? self.state.state : nil - notification_state = self.state ? self.state.state : nil + alert_check = self.check - alert_check = self.check + logger.info "contacts: #{route_ids_by_contact_id.keys.size}" - logger.info "contacts: #{route_ids_by_contact_id.keys.size}" + contacts = route_ids_by_contact_id.empty? ? [] : + Flapjack::Data::Contact.find_by_ids(*route_ids_by_contact_id.keys) + return [] if contacts.empty? - contacts = route_ids_by_contact_id.empty? ? [] : - Flapjack::Data::Contact.find_by_ids(*route_ids_by_contact_id.keys) - return [] if contacts.empty? + # TODO pass in base time from outside (cast to zone per contact), so + # all alerts from this notification use a consistent time - # TODO pass in base time from outside (cast to zone per contact), so - # all alerts from this notification use a consistent time + contact_ids_to_drop = [] - contact_ids_to_drop = [] + route_ids = contacts.inject([]) do |memo, contact| + routes = Flapjack::Data::Route.find_by_ids(*route_ids_by_contact_id[contact.id]) + next memo if routes.empty? - route_ids = contacts.inject([]) do |memo, contact| - routes = Flapjack::Data::Route.find_by_ids(*route_ids_by_contact_id[contact.id]) - next memo if routes.empty? + timezone = contact.time_zone(:default => default_timezone) + routes = routes.select {|route| route.is_occurring_now?(timezone) } - timezone = contact.time_zone(:default => default_timezone) - routes = routes.select {|route| route.is_occurring_now?(timezone) } + contact_ids_to_drop << contact.id if routes.any? {|r| r.drop } - contact_ids_to_drop << contact.id if routes.any? {|r| r.drop } - - memo += routes.map(&:id) - memo - end + memo += routes.map(&:id) + memo + end - logger.info "routes after time: #{route_ids.size}" - return [] if route_ids.empty? + logger.info "routes after time: #{route_ids.size}" + return [] if route_ids.empty? - route_ids -= contact_ids_to_drop.flat_map {|c_id| route_ids_by_contact_id[c_id] } + route_ids -= contact_ids_to_drop.flat_map {|c_id| route_ids_by_contact_id[c_id] } - logger.info "routes after drop: #{route_ids.size}" - return [] if route_ids.empty? + logger.info "routes after drop: #{route_ids.size}" + return [] if route_ids.empty? - Flapjack::Data::Medium.lock(Flapjack::Data::Check, - Flapjack::Data::ScheduledMaintenance, - Flapjack::Data::UnscheduledMaintenance, - Flapjack::Data::Route, - Flapjack::Data::Alert, - Flapjack::Data::Medium, - Flapjack::Data::Contact) do + Flapjack::Data::Medium.lock(Flapjack::Data::Check, + Flapjack::Data::ScheduledMaintenance, + Flapjack::Data::UnscheduledMaintenance, + Flapjack::Data::Route, + Flapjack::Data::Alert, + Flapjack::Data::Medium, + Flapjack::Data::Contact) do - media_ids_by_route_id = Flapjack::Data::Route.intersect(:id => route_ids). - associated_ids_for(:media) + media_ids_by_route_id = Flapjack::Data::Route.intersect(:id => route_ids). + associated_ids_for(:media) - media_ids = Set.new(media_ids_by_route_id.values).flatten.to_a + media_ids = Set.new(media_ids_by_route_id.values).flatten.to_a - logger.info "media from routes: #{media_ids.size}" + logger.info "media from routes: #{media_ids.size}" - alertable_media = Flapjack::Data::Medium.intersect(:id => media_ids, - :transport => transports).all + alertable_media = Flapjack::Data::Medium.intersect(:id => media_ids, + :transport => transports).all - # we want to consider this as 'alerting' for the purpose of rollup - # calculations, if it's failing, even if we won't notify on this media - this_notification_failure = Flapjack::Data::CheckState.failing_states.include?(safe_state_or_ack) && - !(alert_check.in_scheduled_maintenance? || alert_check.in_unscheduled_maintenance?) + # we want to consider this as 'alerting' for the purpose of rollup + # calculations, if it's failing, even if we won't notify on this media + this_notification_failure = Flapjack::Data::CheckState.failing_states.include?(safe_state_or_ack) && + !(alert_check.in_scheduled_maintenance? || alert_check.in_unscheduled_maintenance?) - ok_states = Flapjack::Data::CheckState.ok_states + ['acknowledgement'] + ok_states = Flapjack::Data::CheckState.ok_states + ['acknowledgement'] - this_notification_ok = ok_states.include?(safe_state_or_ack) - is_a_test = 'test'.eql?(safe_state_or_ack) + this_notification_ok = ok_states.include?(safe_state_or_ack) + is_a_test = 'test'.eql?(safe_state_or_ack) - alert_check.alerting_media.add(*alertable_media) if this_notification_failure + alert_check.alerting_media.add(*alertable_media) if this_notification_failure - logger.info "pre-media test: \n" \ - " this_notification_failure = #{this_notification_failure}\n" \ - " this_notification_ok = #{this_notification_ok}\n" \ - " is_a_test = #{is_a_test}" + logger.info "pre-media test: \n" \ + " this_notification_failure = #{this_notification_failure}\n" \ + " this_notification_ok = #{this_notification_ok}\n" \ + " is_a_test = #{is_a_test}" - alertable_media.each_with_object([]) do |medium, memo| + alertable_media.each_with_object([]) do |medium, memo| - logger.info "media test: #{medium.transport}, #{medium.id}" + logger.info "media test: #{medium.transport}, #{medium.id}" - no_previous_notification = medium.last_notification.nil? + no_previous_notification = medium.last_notification.nil? - last_notification_failure = Flapjack::Data::CheckState.failing_states. - include?(medium.last_notification_state) - last_notification_ok = ok_states.include?(medium.last_notification_state) + last_notification_failure = Flapjack::Data::CheckState.failing_states. + include?(medium.last_notification_state) + last_notification_ok = ok_states.include?(medium.last_notification_state) - alerting_check_ids = medium.rollup_threshold.nil? || (medium.rollup_threshold == 0) ? nil : - medium.alerting_checks.ids + alerting_check_ids = medium.rollup_threshold.nil? || (medium.rollup_threshold == 0) ? nil : + medium.alerting_checks.ids - # TODO remove any alerting checks that aren't in failing_checks, - # dynamically calculated + # TODO remove any alerting checks that aren't in failing_checks, + # dynamically calculated - logger.info " alerting_checks: #{alerting_check_ids.inspect}" + logger.info " alerting_checks: #{alerting_check_ids.inspect}" - alert_rollup = if alerting_check_ids.nil? - if 'problem'.eql?(medium.last_rollup_type) + alert_rollup = if alerting_check_ids.nil? + if 'problem'.eql?(medium.last_rollup_type) + 'recovery' + else + nil + end + elsif alerting_check_ids.size >= medium.rollup_threshold + 'problem' + elsif 'problem'.eql?(medium.last_rollup_type) 'recovery' else nil end - elsif alerting_check_ids.size >= medium.rollup_threshold - 'problem' - elsif 'problem'.eql?(medium.last_rollup_type) - 'recovery' - else - nil - end - interval_allows = medium.last_notification.nil? || - ((last_notification_failure && this_notification_failure) && - ((medium.last_notification + medium.interval) < timestamp)) - - logger.info " last_notification_failure = #{last_notification_failure}\n" \ - " last_notification_ok = #{last_notification_ok}" \ - " interval_allows = #{interval_allows}\n" \ - " alert_rollup , last_rollup_type = #{alert_rollup} , #{medium.last_rollup_type}\n" \ - " safe_state_or_ack , last_notification_state = #{safe_state_or_ack} , #{medium.last_notification_state}\n" \ - " no_previous_notification = #{no_previous_notification}\n" - - next unless is_a_test || no_previous_notification || - (last_notification_failure && this_notification_ok) || - (alert_rollup != medium.last_rollup_type) || - (safe_state_or_ack != medium.last_notification_state) || - interval_allows - - alert = Flapjack::Data::Alert.new(:state => safe_state_or_ack, - :state_duration => self.state_duration, - :acknowledgement_duration => self.duration, - :notification_type => self.type, - :rollup => alert_rollup) - - unless alert_rollup.nil? - alerting_checks = Flapjack::Data::Check.find_by_ids(*alerting_check_ids) - alert.rollup_states = alerting_checks.each_with_object({}) do |check, memo| - memo[check.state] ||= [] - memo[check.state] << check.name + interval_allows = medium.last_notification.nil? || + ((last_notification_failure && this_notification_failure) && + ((medium.last_notification + medium.interval) < timestamp)) + + logger.info " last_notification_failure = #{last_notification_failure}\n" \ + " last_notification_ok = #{last_notification_ok}" \ + " interval_allows = #{interval_allows}\n" \ + " alert_rollup , last_rollup_type = #{alert_rollup} , #{medium.last_rollup_type}\n" \ + " safe_state_or_ack , last_notification_state = #{safe_state_or_ack} , #{medium.last_notification_state}\n" \ + " no_previous_notification = #{no_previous_notification}\n" + + next unless is_a_test || no_previous_notification || + (last_notification_failure && this_notification_ok) || + (alert_rollup != medium.last_rollup_type) || + (safe_state_or_ack != medium.last_notification_state) || + interval_allows + + alert = Flapjack::Data::Alert.new(:state => safe_state_or_ack, + :state_duration => self.state_duration, + :acknowledgement_duration => self.duration, + :notification_type => self.type, + :rollup => alert_rollup) + + unless alert_rollup.nil? + alerting_checks = Flapjack::Data::Check.find_by_ids(*alerting_check_ids) + alert.rollup_states = alerting_checks.each_with_object({}) do |check, memo| + memo[check.state] ||= [] + memo[check.state] << check.name + end end - end - unless alert.save - raise "Couldn't save alert: #{alert.errors.full_messages.inspect}" - end + unless alert.save + raise "Couldn't save alert: #{alert.errors.full_messages.inspect}" + end - medium.alerts << alert - alert_check.alerts << alert + medium.alerts << alert + alert_check.alerts << alert - logger.info "alerting with:" - logger.info alert.inspect + logger.info "alerting with:" + logger.info alert.inspect - unless 'test'.eql?(safe_state_or_ack) - medium.last_notification = timestamp - medium.last_notification_state = safe_state_or_ack - medium.last_rollup_type = alert.rollup - medium.save - end + unless 'test'.eql?(safe_state_or_ack) + medium.last_notification = timestamp + medium.last_notification_state = safe_state_or_ack + medium.last_rollup_type = alert.rollup + medium.save + end - memo << alert + memo << alert + end end end end - - - end end end diff --git a/lib/flapjack/gateways/jsonapi.rb b/lib/flapjack/gateways/jsonapi.rb index 5b0848642..1e59d2d64 100644 --- a/lib/flapjack/gateways/jsonapi.rb +++ b/lib/flapjack/gateways/jsonapi.rb @@ -60,7 +60,7 @@ def start request.query_string.length > 0) ? "?#{request.query_string}" : "" if logger.debug? input = env['rack.input'].read - logger.debug("#{request.request_method} #{request.path_info}#{query_string} #{input}") + logger.debug("#{request.request_method} #{request.path_info}#{query_string} Headers: #{headers.inspect}, Body: #{input}") elsif logger.info? input = env['rack.input'].read input_short = input.gsub(/\n/, '').gsub(/\s+/, ' ') @@ -81,8 +81,9 @@ def start else response.body.to_s end + headers_debug = response.headers.to_s logger.debug("Returning #{response.status} for #{request.request_method} " + - "#{request.path_info}#{query_string}, body: #{body_debug}") + "#{request.path_info}#{query_string}, headers: #{headers_debug}, body: #{body_debug}") elsif logger.info? logger.info("Returning #{response.status} for #{request.request_method} " + "#{request.path_info}#{query_string}") diff --git a/lib/flapjack/gateways/jsonapi/helpers/check_presenter.rb b/lib/flapjack/gateways/jsonapi/helpers/check_presenter.rb index 18575232c..2f0eaa0e8 100755 --- a/lib/flapjack/gateways/jsonapi/helpers/check_presenter.rb +++ b/lib/flapjack/gateways/jsonapi/helpers/check_presenter.rb @@ -26,6 +26,7 @@ def initialize(check) end def status + last_change = @check.states.last last_update = @check.last_update last_problem = @check.states.intersect(:state => Flapjack::Data::CheckState.failing_states, :notified => true).last @@ -40,6 +41,7 @@ def status 'in_unscheduled_maintenance' => @check.in_unscheduled_maintenance?, 'in_scheduled_maintenance' => @check.in_scheduled_maintenance?, 'last_update' => (last_update ? last_update.timestamp : nil), + 'last_change' => (last_change ? last_change.timestamp : nil), 'last_problem_notification' => (last_problem ? last_problem.timestamp : nil), 'last_recovery_notification' => (last_recovery ? last_recovery.timestamp : nil), 'last_acknowledgement_notification' => (last_ack ? last_ack.timestamp : nil), diff --git a/lib/flapjack/gateways/jsonapi/rack/json_params_parser.rb b/lib/flapjack/gateways/jsonapi/rack/json_params_parser.rb index f2d8e1962..215b53802 100644 --- a/lib/flapjack/gateways/jsonapi/rack/json_params_parser.rb +++ b/lib/flapjack/gateways/jsonapi/rack/json_params_parser.rb @@ -13,7 +13,7 @@ def call(env) env['rack.request.form_input'] = env['rack.input'] json_data = env['rack.input'].read env['rack.input'].rewind - data = json_data.empty? ? {} : Oj.load(json_data) + data = json_data.empty? ? {} : Flapjack.load_json(json_data) env['rack.request.form_hash'] = data.empty? ? {} : (('application/json-patch+json'.eql?(t)) ? {'ops' => data} : data) end diff --git a/lib/flapjack/gateways/web.rb b/lib/flapjack/gateways/web.rb index a995613d1..75b41b613 100644 --- a/lib/flapjack/gateways/web.rb +++ b/lib/flapjack/gateways/web.rb @@ -43,7 +43,7 @@ def start @api_url = @config['api_url'] if @api_url - if (@api_url =~ /^#{URI::regexp(%w(http https))}$/).nil? + if URI.regexp(['http', 'https']).match(@api_url).nil? @logger.error "api_url is not a valid http or https URI (#{@api_url}), discarding" @api_url = nil end diff --git a/lib/flapjack/gateways/web/public/js/backbone.jsonapi.js b/lib/flapjack/gateways/web/public/js/backbone.jsonapi.js index 2742cf0b9..1ac062511 100644 --- a/lib/flapjack/gateways/web/public/js/backbone.jsonapi.js +++ b/lib/flapjack/gateways/web/public/js/backbone.jsonapi.js @@ -46,6 +46,25 @@ toolbox.batchRequest = function(klass, ids, amount, success) { Backbone.JSONAPIModel = Backbone.Model.extend({ + initialize: function() { + var _persisted; + + this.isPersisted = function () { + if ( !_.isUndefined(_persisted) && !_.isNull(_persisted) ) { + return(_persisted); + } else { + return(!_.isNull(this.id)); + } + }; + this.setPersisted = function(pers) { + _persisted = pers; + }; + }, + + isNew: function() { + return(!this.isPersisted()); + }, + // the following two methods, and batchRequest, should be folded into // Collection.fetch (and Model.fetch?) resolveLink: function(name, klass, superset) { diff --git a/lib/flapjack/gateways/web/public/js/flapjack.js b/lib/flapjack/gateways/web/public/js/flapjack.js index 67fb7e48d..8c42e3685 100644 --- a/lib/flapjack/gateways/web/public/js/flapjack.js +++ b/lib/flapjack/gateways/web/public/js/flapjack.js @@ -44,7 +44,11 @@ $(document).ready(function() { // skip if modal showing if ( $('#contactModal').hasClass('in') ) { return; } - var contactDetailsView = new (Contact.Views.ContactDetails)({model: new (Contact.Model)()}); + var contact = new (Contact.Model)(); + contact.setPersisted(false); + contact.set('id', toolbox.generateUUID()); + + var contactDetailsView = new (Contact.Views.ContactDetails)({model: contact}); contactDetailsView.render(); $('div.modal-dialog').append(contactDetailsView.$el); @@ -75,4 +79,4 @@ $(document).ready(function() { } }); -}); \ No newline at end of file +}); diff --git a/lib/flapjack/gateways/web/public/js/modules/contact.js b/lib/flapjack/gateways/web/public/js/modules/contact.js index a68df12a8..264ea3f75 100644 --- a/lib/flapjack/gateways/web/public/js/modules/contact.js +++ b/lib/flapjack/gateways/web/public/js/modules/contact.js @@ -15,6 +15,7 @@ links: {}, }, initialize: function(){ + Backbone.JSONAPIModel.prototype.initialize.apply(this, arguments); this.on('change', this.setDirty, this); }, toJSON: function() { @@ -57,17 +58,7 @@ this.collection.each(function(contact) { var item = new (Contact.Views.ListItem)({ model: contact }); - var itemEl = item.render().$el; - - itemEl.on('mouseenter', function(e) { - e.stopPropagation(); - $(this).find('td.actions button').css('visibility', 'visible'); - }).on('mouseleave', function(e) { - e.stopPropagation(); - $(this).find('td.actions button').css('visibility', 'hidden'); - }); - - jqel.append(itemEl); + jqel.append(item.render().$el); }); return this; @@ -114,7 +105,7 @@ var context = this; - var deferreds = this.model.resolveLinks({media: Medium.List}); + var deferreds = this.model.resolveLinks({media: Medium.List}); $.when.apply($, deferreds).done( function() { @@ -198,6 +189,7 @@ if ( this.model.isNew() ) { var save_success = function(model, response, options) { + model.setPersisted(true); flapjack.contactList.add(model); $('#contactModal').modal('hide'); }; @@ -281,8 +273,10 @@ transport: transport, address: '', interval: 15, - rollup_threshold: 3, + rollup_threshold: 3 }); + medium.setPersisted(false); + medium.set('id', options.contact.get('id') + '_' + type); context.collection.add(medium); } medium.contact = options.contact; @@ -349,7 +343,10 @@ var attrs = _.pick(model.attributes, changedAttrKeys); if ( model.isNew() ) { model.save(attrs); + // TODO will fail new JSONAPI validation, cleanup model.set('id', model.contact.get('id') + '_' + model.get('transport')); + model.setPersisted(true); + model.contact.addLinked('contacts', 'media', model); } else { model.patch('media', attrs); } diff --git a/lib/flapjack/gateways/web/public/js/modules/medium.js b/lib/flapjack/gateways/web/public/js/modules/medium.js index a4a82c467..a7d5b692f 100644 --- a/lib/flapjack/gateways/web/public/js/modules/medium.js +++ b/lib/flapjack/gateways/web/public/js/modules/medium.js @@ -6,6 +6,7 @@ Medium.Model = Backbone.JSONAPIModel.extend({ name: 'media', initialize: function(){ + Backbone.JSONAPIModel.prototype.initialize.apply(this, arguments); this.on('change', this.setDirty, this); }, defaults: { diff --git a/lib/flapjack/gateways/web/public/js/self_stats.js b/lib/flapjack/gateways/web/public/js/self_stats.js index a83ffe54e..eda88eed7 100644 --- a/lib/flapjack/gateways/web/public/js/self_stats.js +++ b/lib/flapjack/gateways/web/public/js/self_stats.js @@ -66,7 +66,7 @@ $(document).ready(function() { var d = new Date().getTime(); var value = {x: d, y: json.event_queue_length} data[0].values.push(value); - console.log(data[0].values.length); + // console.log(data[0].values.length); // Remove old data, to keep the graph performant if (data[0].values.length > 100) { diff --git a/lib/flapjack/gateways/web/views/edit_contacts.html.erb b/lib/flapjack/gateways/web/views/edit_contacts.html.erb index 88f9f4512..2a931d7fa 100644 --- a/lib/flapjack/gateways/web/views/edit_contacts.html.erb +++ b/lib/flapjack/gateways/web/views/edit_contacts.html.erb @@ -19,13 +19,13 @@ diff --git a/lib/flapjack/processor.rb b/lib/flapjack/processor.rb index 9257a0056..00199d7a6 100755 --- a/lib/flapjack/processor.rb +++ b/lib/flapjack/processor.rb @@ -73,9 +73,9 @@ def initialize(opts = {}) # TODO: set up a separate timer to reset key expiry every minute # and reduce the expiry to, say, five minutes # TODO: remove these keys on process exit - def touch_keys - Flapjack.redis.expire("executive_instance:#{@instance_id}", 1036800) - Flapjack.redis.expire("event_counters:#{@instance_id}", 1036800) + def touch_keys(multi) + multi.expire("executive_instance:#{@instance_id}", 1036800) + multi.expire("event_counters:#{@instance_id}", 1036800) end def start @@ -89,20 +89,18 @@ def start counter_types = ['all', 'ok', 'failure', 'action', 'invalid'] counters = Hash[counter_types.zip(Flapjack.redis.hmget('event_counters', *counter_types))] - Flapjack.redis.multi + Flapjack.redis.multi do |multi| + counter_types.select {|ct| counters[ct].nil? }.each do |counter_type| + multi.hset('event_counters', counter_type, 0) + end - counter_types.select {|ct| counters[ct].nil? }.each do |counter_type| - Flapjack.redis.hset('event_counters', counter_type, 0) + multi.zadd('executive_instances', @boot_time.to_i, @instance_id) + multi.hset("executive_instance:#{@instance_id}", 'boot_time', @boot_time.to_i) + multi.hmset("event_counters:#{@instance_id}", + 'all', 0, 'ok', 0, 'failure', 0, 'action', 0, 'invalid', 0) + touch_keys(multi) end - Flapjack.redis.zadd('executive_instances', @boot_time.to_i, @instance_id) - Flapjack.redis.hset("executive_instance:#{@instance_id}", 'boot_time', @boot_time.to_i) - Flapjack.redis.hmset("event_counters:#{@instance_id}", - 'all', 0, 'ok', 0, 'failure', 0, 'action', 0, 'invalid', 0) - touch_keys - - Flapjack.redis.exec - queue = (@config['queue'] || 'events') loop do @@ -140,19 +138,20 @@ def foreach_on_queue(queue, opts = {}) Flapjack.redis.rpop(queue)) parsed = Flapjack::Data::Event.parse_and_validate(event_json, :logger => @logger) if parsed.nil? - if archive - Flapjack.redis.multi - Flapjack.redis.lrem(archive, 1, event_json) + Flapjack.redis.multi do |multi| + if archive + multi.lrem(archive, 1, event_json) + end + multi.lpush(rejects, event_json) + multi.hincrby('event_counters', 'all', 1) + multi.hincrby("event_counters:#{@instance_id}", 'all', 1) + multi.hincrby('event_counters', 'invalid', 1) + multi.hincrby("event_counters:#{@instance_id}", 'invalid', 1) end - Flapjack.redis.lpush(rejects, event_json) - Flapjack.redis.hincrby('event_counters', 'all', 1) - Flapjack.redis.hincrby("event_counters:#{@instance_id}", 'all', 1) - Flapjack.redis.hincrby('event_counters', 'invalid', 1) - Flapjack.redis.hincrby("event_counters:#{@instance_id}", 'invalid', 1) if archive - Flapjack.redis.exec Flapjack.redis.expire(archive, max_age) end + else Flapjack.redis.expire(archive, max_age) if archive yield Flapjack::Data::Event.new(parsed) if block_given? @@ -214,7 +213,9 @@ def process_event(event) end def update_keys(event, check, timestamp) - touch_keys + Flapjack.redis.multi do |multi| + touch_keys(multi) + end result = true previous_state = nil @@ -229,20 +230,20 @@ def update_keys(event, check, timestamp) case event.type # Service events represent current state of checks on monitored systems when 'service' - Flapjack.redis.multi - if Flapjack::Data::CheckState.ok_states.include?( event.state ) - Flapjack.redis.hincrby('event_counters', 'ok', 1) - Flapjack.redis.hincrby("event_counters:#{@instance_id}", 'ok', 1) - elsif Flapjack::Data::CheckState.failing_states.include?( event.state ) - Flapjack.redis.hincrby('event_counters', 'failure', 1) - Flapjack.redis.hincrby("event_counters:#{@instance_id}", 'failure', 1) - event.id_hash = check.ack_hash - else - Flapjack.redis.hincrby('event_counters', 'invalid', 1) - Flapjack.redis.hincrby("event_counters:#{@instance_id}", 'invalid', 1) - @logger.error("Invalid event received: #{event.inspect}") + Flapjack.redis.multi do |multi| + if Flapjack::Data::CheckState.ok_states.include?( event.state ) + multi.hincrby('event_counters', 'ok', 1) + multi.hincrby("event_counters:#{@instance_id}", 'ok', 1) + elsif Flapjack::Data::CheckState.failing_states.include?( event.state ) + multi.hincrby('event_counters', 'failure', 1) + multi.hincrby("event_counters:#{@instance_id}", 'failure', 1) + event.id_hash = check.ack_hash + else + multi.hincrby('event_counters', 'invalid', 1) + multi.hincrby("event_counters:#{@instance_id}", 'invalid', 1) + @logger.error("Invalid event received: #{event.inspect}") + end end - Flapjack.redis.exec # not available from an unsaved check previous_state = check.id.nil? ? nil : check.states.last @@ -281,15 +282,15 @@ def update_keys(event, check, timestamp) :timestamp => timestamp) action.save - Flapjack.redis.multi - Flapjack.redis.hincrby('event_counters', 'action', 1) - Flapjack.redis.hincrby("event_counters:#{@instance_id}", 'action', 1) - Flapjack.redis.exec + Flapjack.redis.multi do |multi| + multi.hincrby('event_counters', 'action', 1) + multi.hincrby("event_counters:#{@instance_id}", 'action', 1) + end else - Flapjack.redis.multi - Flapjack.redis.hincrby('event_counters', 'invalid', 1) - Flapjack.redis.hincrby("event_counters:#{@instance_id}", 'invalid', 1) - Flapjack.redis.exec + Flapjack.redis.multi do |multi| + multi.hincrby('event_counters', 'invalid', 1) + multi.hincrby("event_counters:#{@instance_id}", 'invalid', 1) + end @logger.error("Invalid event received: #{event.inspect}") end diff --git a/lib/flapjack/version.rb b/lib/flapjack/version.rb index 511bd49c2..e8aad3427 100644 --- a/lib/flapjack/version.rb +++ b/lib/flapjack/version.rb @@ -1,6 +1,6 @@ #!/usr/bin/env ruby module Flapjack - VERSION = "1.1.0" + VERSION = "2.0.0a1" end diff --git a/spec/lib/flapjack/processor_spec.rb b/spec/lib/flapjack/processor_spec.rb index 5021023ff..a2886d544 100644 --- a/spec/lib/flapjack/processor_spec.rb +++ b/spec/lib/flapjack/processor_spec.rb @@ -9,6 +9,7 @@ let(:lock) { double(Monitor) } let(:redis) { double(Redis) } + let(:multi) { double('multi') } before(:each) do allow(Flapjack).to receive(:redis).and_return(redis) @@ -23,29 +24,28 @@ def expect_filters end def expect_counters - expect(redis).to receive(:multi) - expect(redis).to receive(:hset).with(/^executive_instance:/, "boot_time", anything) expect(redis).to receive(:hmget).with('event_counters', 'all', 'ok', 'failure', 'action', 'invalid').and_return([nil, nil, nil, nil]) - expect(redis).to receive(:hset).with('event_counters', 'all', 0) - expect(redis).to receive(:hset).with('event_counters', 'ok', 0) - expect(redis).to receive(:hset).with('event_counters', 'failure', 0) - expect(redis).to receive(:hset).with('event_counters', 'action', 0) - expect(redis).to receive(:hset).with('event_counters', 'invalid', 0) - - expect(redis).to receive(:hmset).with(/^event_counters:/, 'all', 0, 'ok', 0, 'failure', 0, 'action', 0, 'invalid', 0) - expect(redis).to receive(:zadd).with('executive_instances', 0, an_instance_of(String)) - expect(redis).to receive(:expire).with(/^executive_instance:/, anything) - expect(redis).to receive(:expire).with(/^event_counters:/, anything) - - expect(redis).to receive(:exec) + + expect(redis).to receive(:multi).and_yield(multi) + expect(multi).to receive(:hset).with(/^executive_instance:/, "boot_time", anything) + expect(multi).to receive(:hset).with('event_counters', 'all', 0) + expect(multi).to receive(:hset).with('event_counters', 'ok', 0) + expect(multi).to receive(:hset).with('event_counters', 'failure', 0) + expect(multi).to receive(:hset).with('event_counters', 'action', 0) + expect(multi).to receive(:hset).with('event_counters', 'invalid', 0) + + expect(multi).to receive(:hmset).with(/^event_counters:/, 'all', 0, 'ok', 0, 'failure', 0, 'action', 0, 'invalid', 0) + expect(multi).to receive(:zadd).with('executive_instances', 0, an_instance_of(String)) + expect(multi).to receive(:expire).with(/^executive_instance:/, anything) + expect(multi).to receive(:expire).with(/^event_counters:/, anything) end def expect_counters_invalid - expect(redis).to receive(:hincrby).with('event_counters', 'all', 1) - expect(redis).to receive(:hincrby).with(/^event_counters:/, 'all', 1) + expect(multi).to receive(:hincrby).with('event_counters', 'all', 1) + expect(multi).to receive(:hincrby).with(/^event_counters:/, 'all', 1) - expect(redis).to receive(:hincrby).with('event_counters', 'invalid', 1) - expect(redis).to receive(:hincrby).with(/^event_counters:/, 'invalid', 1) + expect(multi).to receive(:hincrby).with('event_counters', 'invalid', 1) + expect(multi).to receive(:hincrby).with(/^event_counters:/, 'invalid', 1) end it "starts up, runs and shuts down (archiving, accepted)" do @@ -93,10 +93,9 @@ def expect_counters_invalid event_json = double('event_json') expect(redis).to receive(:rpoplpush).with('events', /^events_archive:/).twice.and_return(event_json, nil) - expect(redis).to receive(:multi) - expect(redis).to receive(:lrem).with(/^events_archive:/, 1, event_json) - expect(redis).to receive(:lpush).with(/^events_rejected:/, event_json) - expect(redis).to receive(:exec) + expect(redis).to receive(:multi).and_yield(multi) + expect(multi).to receive(:lrem).with(/^events_archive:/, 1, event_json) + expect(multi).to receive(:lpush).with(/^events_rejected:/, event_json) expect(redis).to receive(:expire).with(/^events_archive:/, kind_of(Integer)) expect(Flapjack::Data::Event).to receive(:parse_and_validate). @@ -152,7 +151,8 @@ def expect_counters_invalid expect(Flapjack::Data::Event).to receive(:parse_and_validate). with(event_json, :logger => @logger).and_return(nil) expect(Flapjack::Data::Event).not_to receive(:new) - expect(redis).to receive(:lpush).with(/^events_rejected:/, event_json) + expect(redis).to receive(:multi).and_yield(multi) + expect(multi).to receive(:lpush).with(/^events_rejected:/, event_json) expect(redis).to receive(:brpop).with('events_actions').and_raise(Flapjack::PikeletStop) expect(redis).to receive(:quit) diff --git a/spec/service_consumers/pact_helper.rb b/spec/service_consumers/pact_helper.rb index 94b51c89c..d9a47bdb2 100644 --- a/spec/service_consumers/pact_helper.rb +++ b/spec/service_consumers/pact_helper.rb @@ -1,4 +1,4 @@ -puts "\nSkipping pact verification, API is broken, will fix soon\n\n" +puts "\nSkipping pact verification, pacts need updating, will fix soon\n\n" exit 0 require 'pact/provider/rspec' diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 9d3dd7128..dc5a95ec3 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -4,7 +4,6 @@ add_filter '/spec/' end SimpleCov.at_exit do - Oj.default_options = { :mode => :compat } SimpleCov.result.format! end end diff --git a/spec/support/jsonapi_helper.rb b/spec/support/jsonapi_helper.rb index 42c2444d6..b5d4c6103 100644 --- a/spec/support/jsonapi_helper.rb +++ b/spec/support/jsonapi_helper.rb @@ -47,7 +47,7 @@ def app expect(last_response.headers.keys).to include('Access-Control-Allow-Methods') expect(last_response.headers['Access-Control-Allow-Origin']).to eq("*") unless last_response.status == 204 - expect(Oj.load(last_response.body)).to be_a(Enumerable) + expect(Flapjack.load_json(last_response.body)).to be_a(Enumerable) expect(last_response.headers['Content-Type']).to eq(Flapjack::Gateways::JSONAPI::JSONAPI_MEDIA_TYPE) end end diff --git a/tasks/benchmarks.rake b/tasks/benchmarks.rake deleted file mode 100644 index 5d5fc2bc7..000000000 --- a/tasks/benchmarks.rake +++ /dev/null @@ -1,254 +0,0 @@ -require 'oj' -require 'time' - -namespace :benchmarks do - - # add lib to the default include path - unless $:.include?(File.dirname(__FILE__) + '/../lib/') - $: << File.dirname(__FILE__) + '/../lib' - end - - require 'flapjack/configuration' - require 'flapjack/version' - - def prepare_env - config_file = File.join('tasks', 'support', 'flapjack_config_benchmark.toml') - - config = Flapjack::Configuration.new - config.load( config_file ) - - @config_env = config.all - @redis = Redis.new(config.for_redis) - - if @config_env.nil? || @config_env.empty? - puts "No config data found in '#{config_file}'" - exit(false) - end - end - - def push_event(queue, event) - event['time'] = Time.now.to_i if event['time'].nil? - - begin - event_json = Flapjack.dump_json(event) - rescue Oj::Error => e - if opts[:logger] - opts[:logger].warn("Error serialising event json: #{e}, event: #{event.inspect}") - end - event_json = nil - end - - if event_json - @redis.multi do - @redis.lpush(queue, event_json) - @redis.lpush("#{queue}_actions", "+") - end - end - end - - desc "nukes the redis db, generates the events, runs and shuts down flapjack, generates perftools reports" - task :run => [:reset_redis, :benchmark, :run_flapjack, :reports] do - puts Flapjack.dump_json(@benchmark_data, :indent => 2) - end - - desc "reset the redis database" - task :reset_redis do - FLAPJACK_ENV = 'test' - prepare_env - raise "I'm not going to let you reset your production redis db, sorry about that." if FLAPJACK_ENV.downcase == "production" - puts "db size before: #{@redis.dbsize}" - @redis.flushdb - puts "db size after: #{@redis.dbsize}" - end - - desc "starts flapjack" - task :run_flapjack do - puts "Discovering path to perftools" - perftools = `gem which perftools | tail -1` - if system("if [ ! -d 'artifacts' ] ; then mkdir artifacts ; fi") - puts "we now have an artifacts dir" - else - raise "Problem creating artifacts: #{$?}" - end - time_flapjack_start = Time.now.to_f - puts "Starting flapjack..." - result = system({"FLAPJACK_ENV" => FLAPJACK_ENV, - "CPUPROFILE" => "artifacts/flapjack-perftools-cpuprofile", - "RUBYOPT" => "-r#{perftools}"}, - "bin/flapjack start --no-daemonize --config tasks/support/flapjack_config_benchmark.toml --logfile log/benchmark_test.log") - if result - puts "Flapjack run completed successfully" - else - raise "Problem starting flapjack: #{$?}" - end - @timer_flapjack = Time.now.to_f - time_flapjack_start - end - - desc "generates perftools reports" - task :reports do - @benchmark_data = { 'events_created' => @events_created, - 'flapjack_runtime' => @timer_flapjack, - 'processing_rate' => @events_created.to_f / @timer_flapjack }.merge(@benchmark_parameters) - bytes_written = IO.write('artifacts/benchmark_data.json', Oj.dump(@benchmark_data, :indent => 2)) - puts "benchmark data written to artifacts/benchmark_data.json (#{bytes_written} bytes)" - - if system("pprof.rb --text artifacts/flapjack-perftools-cpuprofile > artifacts/flapjack-perftools-cpuprofile.txt") - puts "Generated perftools.rb text report at artifacts/flapjack-perftools-cpuprofile.txt" - system("head -24 artifacts/flapjack-perftools-cpuprofile.txt") - else - raise "Problem generating perftools.rb text report: #{$?}" - end - if system("pprof.rb --pdf artifacts/flapjack-perftools-cpuprofile > artifacts/flapjack-perftools-cpuprofile.pdf") - puts "Generated perftools.rb pdf report at artifacts/flapjack-perftools-cpuprofile.pdf" - else - raise "Problem generating perftools.rb pdf report: #{$?}" - end - end - - - desc "run benchmark - simulate a stream of events from the check execution system" - # Assumptions: - # - time to failure varies evenly between 1 hour and 1 month - # - time to recovery varies evenly between 10 seconds and 1 week - task :benchmark do - unless RUBY_VERSION.split('.')[0] == '1' && RUBY_VERSION.split('.')[1] == '8' - # Flapjack doesn't support 1.8 or below, so just checking for 1.9 is OK - raise "perftools.rb doesn't work on Ruby 2.0 or greater" - end - - num_checks_per_entity = (ENV['CHECKS_PER_ENTITY'] || 5).to_i - num_entities = (ENV['ENTITIES'] || 100).to_i - interval = (ENV['INTERVAL'] || 60).to_i - hours = (ENV['HOURS'] || 1).to_f - seed = (ENV['SEED'] || 42).to_i - - puts "Behaviour can be modified by setting any combination of the following environment variables: " - puts "CHECKS_PER_ENTITY - #{num_checks_per_entity}" - puts "ENTITIES - #{num_entities}" - puts "INTERVAL - #{interval}" - puts "HOURS - #{hours}" - puts "SEED - #{seed}" - puts "FLAPJACK_ENV - #{FLAPJACK_ENV}" - - raise "INTERVAL must be less than (or equal to) 3600 seconds (1 hour)" unless interval <= 3600 - - cycles_per_hour = (60.0 * 60) / interval - cycles_per_day = (60.0 * 60 * 24) / interval - cycles_per_week = (60.0 * 60 * 24 * 7) / interval - cycles_per_month = (60.0 * 60 * 24 * 7 * 30) / interval - cycles = (hours * cycles_per_hour).to_i - failure_prob_min = 1.0 / cycles_per_month - failure_prob_max = 1.0 / cycles_per_hour - recovery_prob_min = 1.0 / cycles_per_week - recovery_prob_max = 1.0 - initial_ok_prob = 1 - num_checks = num_checks_per_entity * num_entities - - prng = Random.new(seed) - - ok = 0 - critical = 0 - check_id = 1 - entities = (1..num_entities).to_a.inject({}) {|memo, id| - checks = (1..num_checks_per_entity).to_a.inject({}) {|memo_check, id_check| - memo_check[check_id] = {:name => "Check Type #{id_check}", - :state => ( prng.rand < initial_ok_prob ? 'OK' : 'CRITICAL' ), - :p_failure => prng.rand(failure_prob_min..failure_prob_max), - :p_recovery => prng.rand(recovery_prob_min..recovery_prob_max)} - ok += 1 if memo_check[check_id][:state] == 'OK' - critical += 1 if memo_check[check_id][:state] == 'CRITICAL' - check_id += 1 - memo_check - } - memo[id] = checks - memo - } - #puts "ok: #{ok * 100.0 / num_checks}% (#{ok}), critical: #{100.0 * critical / num_checks}% (#{critical})" - - events_created = 0 - ok_to_critical = 0 - critical_to_ok = 0 - ok_events = 0 - critical_events = 0 - state_changes = 0 - - (0..cycles).to_a.each {|i| - changes = 0 - ok = 0 - critical = 0 - summary = "You tell me summer's here \nand the time is wrong \n" - summary << "You tell me winter's here \nAnd your days are getting long" - entities.each_pair {|entity_id, checks| - checks.each_pair {|check_id, check| - changed = false - previous_state = check[:state] - case previous_state - when "OK" - if prng.rand < check[:p_failure] - check[:state] = "CRITICAL" - changed = true - changes += 1 - ok_to_critical += 1 - end - when "CRITICAL" - if prng.rand < check[:p_recovery] - check[:state] = "OK" - changed = true - changes += 1 - critical_to_ok += 1 - end - end - ok += 1 if check[:state] == 'OK' - critical += 1 if check[:state] == 'CRITICAL' - - event = { 'entity' => "entity_#{entity_id}.example.com", - 'check' => check[:name], - 'type' => 'service', - 'state' => check[:state], - 'summary' => summary } - - push_event('events', event) - events_created += 1 - } - } - ok_events += ok - critical_events += critical - state_changes += changes - - #puts "ok: #{100.0 * ok / num_checks}% (#{ok}), critical: #{100.0 * critical / num_checks}% (#{critical}), changed: #{100.0 * changes / num_checks}% (#{changes})" - - } - puts "created #{events_created} events:" - puts " OK: #{ok_events} (#{ (100.0 * ok_events / events_created).round(1)}%)" - puts " CRITICAL: #{critical_events} (#{ (100.0 * critical_events / events_created).round(1)}%)" - puts "containing #{state_changes} state changes (#{ (100.0 * state_changes / events_created).round(1)}%):" - puts " OK -> CRITICAL: #{ok_to_critical} (#{ (100.0 * ok_to_critical / events_created).round(1)}%)" - puts " CRITICAL -> OK: #{critical_to_ok} (#{ (100.0 * critical_to_ok / events_created).round(1)}%)" - - @events_created = events_created - @benchmark_parameters = { 'events_created' => events_created, - 'ok_to_critical' => ok_to_critical, - 'critical_to_ok' => critical_to_ok, - 'checks_per_entity' => num_checks_per_entity, - 'entities' => num_entities, - 'interval' => interval, - 'hours' => hours, - 'cycles' => cycles, - 'failure_prob_min' => failure_prob_min, - 'failure_prob_max' => failure_prob_max, - 'recovery_prob_min' => recovery_prob_min, - 'recovery_prob_max' => recovery_prob_max, - 'initial_ok_prob' => initial_ok_prob, - 'seed' => seed, - 'flapjack_env' => FLAPJACK_ENV, - 'version' => Flapjack::VERSION, - 'git_last_commit' => `git rev-parse HEAD`.chomp, - 'git_version' => `git describe --long --dirty --abbrev=10 --tags`.chomp, - 'git_branch' => `git status --porcelain -b | head -1 | cut -d ' ' -f 2`.chomp, - 'ruby_build' => `ruby --version`.chomp, - 'time' => Time.new.iso8601, - 'hostname' => `hostname -f`.chomp, - 'uname' => `uname -a`.chomp } - end - -end