class Flapjack::CLI::Migrate

Public Class Methods

new(global_options, options) click to toggle source
# File lib/flapjack/cli/migrate.rb, line 53
def initialize(global_options, options)
  @global_options = global_options
  @options = options

  config = Flapjack::Configuration.new

  if options[:source].nil? || options[:source].strip.empty?
    config.load(global_options[:config])
    config_env = config.all
  end

  @source_redis_options = config.for_redis
end

Public Instance Methods

to_v2() click to toggle source
# File lib/flapjack/cli/migrate.rb, line 67
def to_v2
  source_addr = (@options[:source].nil? || @options[:source].strip.empty?) ?
    @source_redis_options : @options[:source]

  @source_redis = case source_addr
  when Hash
    Redis.new(source_addr.merge(:driver => :hiredis))
  when String
    Redis.new(:url => source_addr, :driver => :hiredis)
  else
    exit_now! "could not understand source Redis config"
  end

  @source_redis_version = @source_redis.info['redis_version']

  dest_addr = @options[:destination]
  dest_redis = Redis.new(:url => dest_addr, :driver => :hiredis)

  # Zermelo.logger = ::Logger.new('/tmp/zermelo_migrate.log')

  do_rule_migration = @options[:rules]

  Zermelo.redis = dest_redis

  dest_db_size = Zermelo.redis.dbsize
  if dest_db_size > 0
    if @options[:force]
      Zermelo.redis.flushdb
      puts "Cleared #{@options[:destination]} db"
    else
      exit_now! "Destination db #{@options[:destination]} has " \
        "#{dest_db_size} keys, and the --force option was not provided"
    end
  end

  # key is old contact_id, value = new contact id
  @contact_id_cache = {}

  # key is entity_name:check_name, value is zermelo record
  @check_name_cache = {}

  # key is tag name, value is zermelo record
  @tag_name_cache = {}

  # cache old entity/check <-> contact linkage, use as limiting set in
  # rule construction
  @check_ids_by_contact_id_cache = {}

  @current_entity_names = @source_redis.zrange('current_entities', 0, -1)
  @current_check_names = source_keys_matching('current_checks:?*')

  @time = Time.now

  puts "#{Time.now.iso8601} Migrating contacts and media"
  migrate_contacts_and_media

  puts "#{Time.now.iso8601} Migrating checks"
  migrate_checks

  puts "Migrating check states & actions"
  Flapjack::Data::Check.lock(
    Flapjack::Data::State,
    Flapjack::Data::ScheduledMaintenance,
    Flapjack::Data::UnscheduledMaintenance
  ) do

    idx = 0
    check_count = Flapjack::Data::Check.count

    Flapjack::Data::Check.sort(:name).each do |check|
      idx += 1
      puts "#{Time.now.iso8601} #{idx}/#{check_count} #{check.name}"
      migrate_states(check)
      migrate_actions(check)
      migrate_scheduled_maintenances(check)
      migrate_unscheduled_maintenances(check)

      if (idx % 500) == 0
        # reopen connections -- avoid potential timeout for long-running jobs
        @source_redis.quit
        @source_redis = case source_addr
        when Hash
          Redis.new(source_addr.merge(:driver => :hiredis))
        when String
          Redis.new(:url => source_addr, :driver => :hiredis)
        end

        Zermelo.redis.quit
        Zermelo.redis = Redis.new(:url => dest_addr, :driver => :hiredis)
      end
    end
  end

  if do_rule_migration
    puts "#{Time.now.iso8601} Migrating contact/entity linkages"
    migrate_contact_entity_linkages

    puts "#{Time.now.iso8601} Migrating rules"
    migrate_rules
  end

rescue Exception => e
  puts e.message
  trace = e.backtrace.join("\n")
  puts trace
  # TODO output data, set exit status
  exit 1
end

Private Instance Methods

field_from_rule_json(rule, field) click to toggle source
# File lib/flapjack/cli/migrate.rb, line 509
def field_from_rule_json(rule, field)
  field_json = rule[field]
  return if field_json.nil? || field_json.empty?
  Flapjack.load_json(field_json)
end
find_check(entity_name, check_name, opts = {}) click to toggle source
# File lib/flapjack/cli/migrate.rb, line 799
def find_check(entity_name, check_name, opts = {})
  new_check_name = "#{entity_name}:#{check_name}"
  check = @check_name_cache[new_check_name]

  if check.nil?
    if opts[:create]
      enabled = @current_entity_names.include?(entity_name) &&
                @source_redis.zrange("current_checks:#{entity_name}", 0, -1).include?(check_name)
      check = Flapjack::Data::Check.new(:name => new_check_name,
        :enabled => enabled)
      check.save
      raise check.errors.full_messages.join(", ") unless check.persisted?
      # puts "adding #{new_check_name} to check cache"
      @check_name_cache[new_check_name] = check
    else
      puts "No check found for '#{new_check_name}'"
    end
  end

  check
end
find_contact(old_contact_id) click to toggle source
# File lib/flapjack/cli/migrate.rb, line 789
def find_contact(old_contact_id)
  new_contact_id = @contact_id_cache[old_contact_id]
  return if new_contact_id.nil?

  contact = Flapjack::Data::Contact.find_by_id(new_contact_id)
  raise "No contact found for old id '#{old_contact_id}', new id '#{new_contact_id}'" if contact.nil?

  contact
end
find_tag(tag_name, opts = {}) click to toggle source
# File lib/flapjack/cli/migrate.rb, line 821
def find_tag(tag_name, opts = {})
  tag = @tag_name_cache[tag_name]

  if tag.nil? && opts[:create]
    tag = Flapjack::Data::Tag.new(:name => tag_name)
    tag.save
    raise tag.errors.full_messages.join(", ") unless tag.persisted?
    @tag_name_cache[tag_name] = tag
  end

  tag
end
migrate_actions(check) click to toggle source

depends on checks, states

# File lib/flapjack/cli/migrate.rb, line 417
def migrate_actions(check)
  actions_key = "#{check.name}:actions"
  return unless @source_redis.exists(actions_key)

  states_to_add = []

  paginate_action(:hash, 100, actions_key) do |timestamp, action|
    timestamp_f = timestamp.to_f
    timestamp_i = timestamp.to_i

    start_range = Zermelo::Filters::IndexRange.new(nil, timestamp_f, :by_score => true)
    previous_state = check.states.intersect(:created_at => start_range).last
    previous_cond = previous_state.nil? ? nil : previous_state.condition

    state = Flapjack::Data::State.new(
      :action => action,
      :condition  => previous_cond,
      :created_at => timestamp_i,
      :updated_at => timestamp_i
    )

    raise state.errors.full_messages.join(", ") unless state.valid?
    states_to_add << state
  end

  return if states_to_add.empty?
  states_to_add.map(&:save)
  check.states.add(*states_to_add)
end
migrate_checks() click to toggle source
# File lib/flapjack/cli/migrate.rb, line 238
def migrate_checks
  all_entity_names_by_id = @source_redis.hgetall('all_entity_names_by_id')

  entity_tags_by_entity_name = {}

  source_keys_matching('entity_tag:?*').each do |entity_tag_key|
    entity_tag_key =~ /\Aentity_tag:(#{TAG_PATTERN_FRAGMENT})\z/
    entity_tag = Regexp.last_match(1)
    raise "Bad regex for '#{entity_tag_key}'" if entity_tag.nil?

    entity_ids = @source_redis.smembers(entity_tag_key)

    entity_ids.each do |entity_id|
      entity_name = all_entity_names_by_id[entity_id]
      raise "No entity name for id #{entity_id}" if entity_name.nil? || entity_name.empty?

      entity_tags_by_entity_name[entity_name] ||= []
      entity_tags_by_entity_name[entity_name] << entity_tag
    end
  end

  source_keys_matching("all_checks:?*") do |all_checks_key|
    all_checks_key =~ /\Aall_checks:(#{ENTITY_PATTERN_FRAGMENT})\z/

    entity_name = Regexp.last_match(1)
    raise "Bad regex for '#{all_checks_key}'" if entity_name.nil?

    entity_tags = entity_tags_by_entity_name[entity_name]

    all_check_names     = @source_redis.zrange(all_checks_key, 0, -1).reject {|k| k.empty? }
    current_check_names = @source_redis.zrange("current_checks:#{entity_name}", 0, -1)

    all_check_names.each do |check_name|
      check = find_check(entity_name, check_name, :create => true)
      check.enabled = current_check_names.include?(check_name)
      check.save
      raise check.errors.full_messages.join(", ") unless check.persisted?

      check.tags << find_tag("entity_#{entity_name}", :create => true)

      unless entity_tags.nil? || entity_tags.empty?
        tags = entity_tags.collect do |entity_tag|
          find_tag("entitytag_#{entity_tag}", :create => true)
        end
        check.tags.add(*tags)
      end
    end
  end
end
migrate_contact_entity_linkages() click to toggle source

depends on contacts & checks

# File lib/flapjack/cli/migrate.rb, line 290
def migrate_contact_entity_linkages
  all_entity_names_by_id = @source_redis.hgetall('all_entity_names_by_id')

  source_keys_matching('contacts_for:?*').each do |contacts_for_key|

    contacts_for_key =~ /\Acontacts_for:(#{ID_PATTERN_FRAGMENT})(?::(#{CHECK_PATTERN_FRAGMENT}))?\z/
    entity_id   = Regexp.last_match(1)
    check_name  = Regexp.last_match(2)
    if entity_id.nil?
      raise "Bad regex for '#{contacts_for_key}'"
    end

    entity_name = all_entity_names_by_id[entity_id]

    contact_ids = @source_redis.smembers(contacts_for_key)
    next if contact_ids.empty?

    contacts = contact_ids.collect {|c_id| find_contact(c_id) }

    contacts.each do |contact|
      @check_ids_by_contact_id_cache[contact.id] ||= []
    end

    tag_for_check = proc do |en, cn, tn|
      check = find_check(en, cn)
      check.tags << find_tag(tn, :create => true)

      contacts.each do |contact|
        @check_ids_by_contact_id_cache[contact.id] << check.id
      end
    end

    if check_name.nil?
      # interested in entity, so apply to all checks for that entity
      tag_name = "entity_#{entity_name}"

      all_checks_for_entity = @source_redis.zrange("all_checks:#{entity_name}", 0, -1)
      all_checks_for_entity.each do |cn|
        next if cn.empty?
        tag_for_check.call(entity_name, cn, tag_name)
      end
    else
      # interested in check
      tag_for_check.call(entity_name, check_name,
        "check_#{entity_name}:#{check_name}")
    end
  end
end
migrate_contacts_and_media() click to toggle source

no dependencies

# File lib/flapjack/cli/migrate.rb, line 179
def migrate_contacts_and_media
  source_keys_matching('contact:?*').each do |contact_key|
    contact_key =~ /\Acontact:(#{ID_PATTERN_FRAGMENT})\z/
    contact_id = Regexp.last_match(1)
    raise "Bad regex for '#{contact_key}'" if contact_id.nil?

    contact_data = @source_redis.hgetall(contact_key).merge(:id => contact_id)

    timezone = @source_redis.get("contact_tz:#{contact_id}")

    media_addresses = @source_redis.hgetall("contact_media:#{contact_id}")
    media_intervals = @source_redis.hgetall("contact_media_intervals:#{contact_id}")
    media_rollup_thresholds = @source_redis.hgetall("contact_media_rollup_thresholds:#{contact_id}")

    contact = Flapjack::Data::Contact.new(:name =>
        "#{contact_data['first_name']} #{contact_data['last_name']}",
      :timezone => timezone)
    if contact.valid?
      contact.save
    else
      puts "Invalid contact: #{contact.errors.full_messages.join(", ")}  #{contact.inspect}"
      next
    end

    media_addresses.each_pair do |media_type, address|
      medium_data = if 'pagerduty'.eql?(media_type)
        pagerduty_credentials =
          @source_redis.hgetall("contact_pagerduty:#{contact_id}")

        {
          :transport => 'pagerduty',
          :address => address,
          :pagerduty_subdomain => pagerduty_credentials['subdomain'],
          :pagerduty_token => pagerduty_credentials['token'],
          :pagerduty_ack_duration => nil
        }
      else
        rut = media_rollup_thresholds[media_type]
        {
          :transport => media_type,
          :address => address,
          :interval => media_intervals[media_type].to_i,
          :rollup_threshold => rut.nil? ? nil : rut.to_i
        }
      end

      medium = Flapjack::Data::Medium.new(medium_data)
      if medium.valid?
        medium.save
        contact.media << medium
      else
        puts "Invalid medium: #{medium.errors.full_messages.join(", ")}  #{medium.inspect}"
      end
    end

    @contact_id_cache[contact_id] = contact.id
  end
end
migrate_rules() click to toggle source

depends on contacts, media, checks

# File lib/flapjack/cli/migrate.rb, line 516
def migrate_rules
  contact_counts_by_id = {}
  check_counts_by_id = {}

  source_keys_matching('contact_notification_rules:?*').each do |rules_key|

    rules_key =~ /\Acontact_notification_rules:(#{ID_PATTERN_FRAGMENT})\z/
    contact_id = Regexp.last_match(1)
    raise "Bad regex for '#{rules_key}'" if contact_id.nil?

    contact = find_contact(contact_id)

    contact_num = contact_counts_by_id[contact.id]
    if contact_num.nil?
      contact_num = contact_counts_by_id.size + 1
      contact_counts_by_id[contact.id] = contact_num
    end

    check_ids = @check_ids_by_contact_id_cache[contact.id]

    Flapjack::Data::Rule.lock(
      Flapjack::Data::Check,
      Flapjack::Data::Tag,
      Flapjack::Data::Contact,
      Flapjack::Data::Medium
    ) do

      new_rule_ids = []

      rule_ids = @source_redis.smembers(rules_key)
      rule_ids.each do |rule_id|
        rule_data = @source_redis.hgetall("notification_rule:#{rule_id}")

        old_time_restrictions_json = rule_data['time_restrictions']

        time_restrictions = if old_time_restrictions_json.nil? || old_time_restrictions_json.empty?
           nil
        else
          old_time_restrictions = Flapjack.load_json(old_time_restrictions_json)
          if old_time_restrictions.nil? || old_time_restrictions.empty?
            []
          elsif old_time_restrictions.is_a?(Hash)
            [rule_time_restriction_to_icecube_schedule(old_time_restrictions, contact.time_zone)]
          elsif old_time_restrictions.is_a?(Array)
            old_time_restrictions.map {|t| rule_time_restriction_to_icecube_schedule(t, contact.time_zone)}
          end
        end

        filter_tag_ids = Set.new

        old_entities = field_from_rule_json(rule_data, 'entities')
        entity_names = case old_entities
        when String
          Set.new([old_entities])
        when Array
          Set.new(old_entities)
        else
          nil
        end

        old_regex_entities = field_from_rule_json(rule_data, 'regex_entities')
        entity_regexes = case old_regex_entities
        when String
          Set.new([Regexp.new(old_regex_entities)])
        when Array
          Set.new(old_regex_entities.map {|re| Regexp.new(re) })
        else
          nil
        end

        Flapjack::Data::Tag.intersect(:name => '/^manage_service_\d+_.+$/').each do |tag|
          next if tag.name.nil? ||
            (tag.name !~ /^manage_service_(\d+)_(.+)$/)
          # eldest_service_id = Regexp.last_match(1)
          service_name = Regexp.last_match(2)

          next unless entity_names.include?(service_name) ||
            entity_regexes.all? {|re| !re.match(service_name).nil? }
          filter_tag_ids << tag.id
        end


        old_tags = field_from_rule_json(rule_data, 'tags')
        tags = case old_tags
        when String
          Set.new([old_tags])
        when Array
          Set.new(old_tags)
        else
          nil
        end

        old_regex_tags = field_from_rule_json(rule_data, 'regex_tags')
        tag_regexes = case old_regex_tags
        when String
          Set.new([Regexp.new(old_regex_tags)])
        when Array
          Set.new(old_regex_tags.map {|re| Regexp.new(re) })
        else
          nil
        end

        # start with the initial limited visibility range (overall scope)
        old_tags_checks = Flapjack::Data::Check.intersect(:id => check_ids)

        # then limit further by checks with names containing tag words
        unless tags.nil? || tags.empty?
          old_tags_checks = tags.inject(old_tags_checks) do |memo, tag|
            memo.intersect(:name => /(?: |^)#{Regexp.escape(tag)}(?: |$)/)
          end
        end

        # and by checks with names matching regexes
        unless tag_regexes.nil? || tag_regexes.empty?
          old_tags_checks = tag_regexes.inject(old_tags_checks) do |memo, tag_regex|
            memo.intersect(:name => /#{tag_regex}/)
          end
        end

        unless old_tags_checks.empty?
          tag = Flapjack::Data::Tag.new(:name => "migrated_rule_#{rule_id}")
          tag.save!
          tag.checks.add_ids(*old_tags_checks.ids)
          filter_tag_ids << tag.id
        end

        normal_rules_to_create = []
        filter_rules_to_create = []

        conditions_by_blackhole_and_media = {false => {},
                                             true => {}}

        Flapjack::Data::Condition.unhealthy.keys.each do |fail_state|
          media_types = Flapjack.load_json(rule_data["#{fail_state}_media"])
          next if media_types.nil? || media_types.empty?

          media_types_str = media_types.sort.join("|")
          blackhole = !!Flapjack.load_json(rule_data["#{fail_state}_blackhole"])
          conditions_by_blackhole_and_media[blackhole][media_types_str] ||= []
          conditions_by_blackhole_and_media[blackhole][media_types_str] << fail_state
        end

        # multiplier -- conditions_by_blackhole_and_media[false].size +
        #               conditions_by_blackhole_and_media[true].size

        [false, true].each do |blackhole|
           normal_rules_to_create += conditions_by_blackhole_and_media[blackhole].each_with_object([]) do |(media_types_str, fail_states), memo|
            media_types = media_types_str.split('|')

            rule_media_ids = contact.media.intersect(:transport => media_types).ids
            # if no media to communicate by, don't save the rule
            next if rule_media_ids.empty?

            memo << {
              :enabled         => true,
              :blackhole       => blackhole,
              :strategy        => 'global', # filter rule also applies
              :conditions_list => fail_states.sort.join(','),
              :medium_ids      => rule_media_ids
            }
          end
        end

        # multiply by number of applied time restrictions, if any
        unless time_restrictions.empty?
          replace_rules = time_restrictions.each_with_object([]) do |tr, memo|
            memo += normal_rules_to_create.collect do |r|
              r.merge(:time_restriction => tr)
            end
          end

          normal_rules_to_create = replace_rules
        end

        unless filter_tag_ids.empty?
          filter_data = {
            :blackhole => true,
            :strategy => 'no_tag'
          }

          filter_rules = normal_rules_to_create.each_with_object([]) do |r, memo|
            memo << r.merge(filter_data) unless r[:blackhole]
          end

          filter_rules_to_create += filter_rules
        end

        new_rule_ids += normal_rules_to_create.collect do |r|
          new_rule_id = SecureRandom.uuid
          r[:id] = new_rule_id
          medium_ids = r.delete(:medium_ids)
          rule = Flapjack::Data::Rule.new(r)
          rule.save!
          rule.media.add_ids(*medium_ids)
          new_rule_id
        end

        new_rule_ids += filter_rules_to_create.collect do |r|
          new_rule_id = SecureRandom.uuid
          r[:id] = new_rule_id
          medium_ids = r.delete(:medium_ids)
          rule = Flapjack::Data::Rule.new(r)
          rule.save!
          rule.media.add_ids(*medium_ids)
          rule.tags.add_ids(*filter_tag_ids) unless filter_tag_ids.empty?
          new_rule_id
        end
      end

      contact.rules.add_ids(*new_rule_ids) unless new_rule_ids.empty?
    end
  end
end
migrate_scheduled_maintenances(check) click to toggle source

depends on checks

# File lib/flapjack/cli/migrate.rb, line 448
def migrate_scheduled_maintenances(check)
  sm_key = "#{check.name}:scheduled_maintenances"
  return unless @source_redis.exists(sm_key)

  sched_maints_to_add = []

  paginate_action(:zset, 100, sm_key) do |timestamp, duration|
    timestamp_i = timestamp.to_i
    duration_i  = duration.to_i

    summary = @source_redis.get("#{check.name}:#{timestamp}:scheduled_maintenance:summary")

    sched_maint = Flapjack::Data::ScheduledMaintenance.new(
      :start_time => timestamp_i,
      :end_time => timestamp_i + duration_i,
      :summary => summary
    )

    if sched_maint.valid?
      sched_maints_to_add << sched_maint
    else
      puts "Invalid scheduled maintenance: #{sched_maint.errors.full_messages.join(", ")}  #{sched_maint.inspect}"
    end
  end

  return if sched_maints_to_add.empty?
  sched_maints_to_add.map(&:save)
  check.scheduled_maintenances.add(*sched_maints_to_add)
end
migrate_states(check) click to toggle source

depends on checks

# File lib/flapjack/cli/migrate.rb, line 340
def migrate_states(check)
  states_key = "#{check.name}:states"
  return unless @source_redis.exists(states_key)

  last_notification_types = %w(warning critical unknown recovery
                               acknowledgement)

  ln_keys = last_notification_types.map {|t| "#{check.name}:last_#{t}_notification" }
  ln_data = @source_redis.mget(*ln_keys)
  last_notifications = Hash[ *(last_notification_types.map(&:to_sym).zip(ln_data).flatten(1)) ]

  current_condition = nil
  most_severe = nil

  states_to_add = []

  paginate_action(:list, 100, states_key) do |timestamp|
    ect = "#{check.name}:#{timestamp}"
    ts_keys = %w(state summary details count perfdata).map {|k| "#{ect}:#{k}"}
    condition, summary, details, count, perfdata_json =
      @source_redis.mget(*ts_keys)

    current_condition = condition unless condition.nil?

    timestamp_i = timestamp.to_i

    state = Flapjack::Data::State.new(
      :condition => current_condition,
      :created_at => timestamp_i,
      :updated_at => timestamp_i,
      :summary => summary,
      :details => details,
      :perfdata_json => perfdata_json
    )
    raise state.errors.full_messages.join(", ") unless state.valid?

    unless condition.nil?
      if Flapjack::Data::Condition.healthy?(condition)
        most_severe = nil
      elsif Flapjack::Data::Condition.unhealthy.has_key?(condition) &&
        (most_severe.nil? ||
         (Flapjack::Data::Condition.unhealthy[condition] <
            Flapjack::Data::Condition.unhealthy[most_severe.condition]))

        most_severe = state
      end

      notif_state = 'ok'.eql?(condition) ? :recovery : condition.to_sym
      if timestamp.to_i == last_notifications[notif_state]
        check.latest_notifications << state
      end
    end

    states_to_add << state
  end

  unless states_to_add.empty?
    states_to_add.map(&:save)
    check.states.add(*states_to_add)

    unless current_condition.nil?
      check.condition = current_condition
      check.failing = !Flapjack::Data::Condition.healthy?(current_condition)
      if check.valid?
        check.save
      else
        puts "Invalid check: #{check.errors.full_messages.join(", ")}  #{check.inspect}"
      end
    end

    unless most_severe.nil?
      check.most_severe = most_severe
    end
  end
end
migrate_unscheduled_maintenances(check) click to toggle source

depends on checks

# File lib/flapjack/cli/migrate.rb, line 479
def migrate_unscheduled_maintenances(check)
  usm_key = "#{check.name}:unscheduled_maintenances"
  return unless @source_redis.exists(usm_key)

  unsched_maints_to_add = []

  paginate_action(:zset, 100, usm_key) do |timestamp, duration|
    timestamp_i = timestamp.to_i
    duration_i  = duration.to_i

    summary = @source_redis.get("#{check.name}:#{timestamp}:unscheduled_maintenance:summary")

    unsched_maint = Flapjack::Data::UnscheduledMaintenance.new(
      :start_time => timestamp_i,
      :end_time => timestamp_i + duration_i,
      :summary => summary
    )

    if unsched_maint.valid?
      unsched_maints_to_add << unsched_maint
    else
      puts "Invalid unscheduled maintenance: #{unsched_maint.errors.full_messages.join(", ")}  #{unsched_maint.inspect}"
    end
  end

  return if unsched_maints_to_add.empty?
  unsched_maints_to_add.map(&:save)
  check.unscheduled_maintenances.add(*unsched_maints_to_add)
end
paginate_action(type, per_page, key) { |k, v| ... } click to toggle source
# File lib/flapjack/cli/migrate.rb, line 740
def paginate_action(type, per_page, key, &block)
  if ((@source_redis_version.split('.') <=> ['2', '8', '0']) == 1) && [:hash, :zset].include?(type)
    case type
    when :hash
      @source_redis.hscan_each(key, :count => per_page, &block)
    when :zset
      @source_redis.zscan_each(key, :count => per_page, &block)
    end
  else
    total = case type
    when :hash
      @source_redis.hlen(key)
    when :list
      @source_redis.llen(key)
    when :zset
      @source_redis.zcard(key)
    end
    return unless total > 0

    current = 0

    h_keys = @source_redis.hkeys(key) if :hash.eql?(type)

    loop do
      window_end = [current + per_page, total].min - 1
      items = case type
      when :hash
        h_key_window = h_keys[current..window_end]
        Hash[ *(@source_redis.hmget(key, *h_key_wind).flatten(1)) ]
      when :list
        @source_redis.lrange(key, current, window_end)
      when :zset
        Hash[ *(@source_redis.zrange(key, current, window_end, :with_scores => true).flatten(1)) ]
      end

      case type
      when :hash, :zset
        items.each_pair { |k, v| yield(k, v) }
      when :list
        items.each { |item| yield(item) }
      end

      current += items.size
      break if current >= total
    end
  end
end
rule_prepare_time_restriction(time_restriction, timezone = nil) click to toggle source
# File lib/flapjack/cli/migrate.rb, line 858
def rule_prepare_time_restriction(time_restriction, timezone = nil)
  # this will hand back a 'deep' copy
  tr = symbolize(time_restriction)

  return unless (tr.has_key?(:start_time) || tr.has_key?(:start_date)) &&
    (tr.has_key?(:end_time) || tr.has_key?(:end_date))

  # exrules is deprecated in latest ice_cube, but may be stored in data
  # serialised from earlier versions of the gem
  # ( https://github.com/flapjack/flapjack/issues/715 )
  tr.delete(:exrules)

  parsed_time = proc {|tr, field|
    if t = tr.delete(field)
      t = t.dup
      t = t[:time] if t.is_a?(Hash)

      if t.is_a?(Time)
        t
      else
        begin; (timezone || Time).parse(t); rescue ArgumentError; nil; end
      end
    else
      nil
    end
  }

  start_time = parsed_time.call(tr, :start_date) || parsed_time.call(tr, :start_time)
  end_time   = parsed_time.call(tr, :end_date) || parsed_time.call(tr, :end_time)

  return unless start_time && end_time

  tr[:start_time] = timezone ?
                      {:time => start_time, :zone => timezone.name} :
                      start_time

  tr[:end_time]   = timezone ?
                      {:time => end_time, :zone => timezone.name} :
                      end_time

  tr[:duration]   = end_time - start_time

  # check that rrule types are valid IceCube rule types
  return unless tr[:rrules].is_a?(Array) &&
    tr[:rrules].all? {|rr| rr.is_a?(Hash)} &&
    (tr[:rrules].map {|rr| rr[:rule_type]} -
     ['Daily', 'Hourly', 'Minutely', 'Monthly', 'Secondly',
      'Weekly', 'Yearly']).empty?

  # rewrite Weekly to IceCube::WeeklyRule, etc
  tr[:rrules].each {|rrule|
    rrule[:rule_type] = "IceCube::#{rrule[:rule_type]}Rule"
  }

  # TODO does this need to check classes for the following values?
  # "validations": {
  #   "day": [1,2,3,4,5]
  # },
  # "interval": 1,
  # "week_start": 0

  tr
end
rule_time_restriction_to_icecube_schedule(tr, timezone, opts = {}) click to toggle source

NB: ice_cube doesn’t have much rule data validation, and has problems with infinite loops if the data can’t logically match; see

https://github.com/seejohnrun/ice_cube/issues/127 &
https://github.com/seejohnrun/ice_cube/issues/137

We may want to consider some sort of timeout-based check around anything that could fall into that.

We don’t want to replicate IceCube’s from_hash behaviour here, but we do need to apply some sanity checking on the passed data.

# File lib/flapjack/cli/migrate.rb, line 843
def rule_time_restriction_to_icecube_schedule(tr, timezone, opts = {})
  return if tr.nil? || !tr.is_a?(Hash) ||
            timezone.nil? || !timezone.is_a?(ActiveSupport::TimeZone)
  prepared_restrictions = rule_prepare_time_restriction(tr, timezone)
  return if prepared_restrictions.nil?
  IceCube::Schedule.from_hash(prepared_restrictions)
rescue ArgumentError => ae
  if logger = opts[:logger]
    logger.error "Couldn't parse rule data #{e.class}: #{e.message}"
    logger.error prepared_restrictions.inspect
    logger.error e.backtrace.join("\n")
  end
  nil
end
source_keys_matching(key_pat, &block) click to toggle source

###########################################################################

# File lib/flapjack/cli/migrate.rb, line 732
def source_keys_matching(key_pat, &block)
  if (@source_redis_version.split('.') <=> ['2', '8', '0']) == 1
    @source_redis.scan_each(:match => key_pat, &block)
  else
    @source_redis.keys(key_pat).each {|k| block.call(k) }
  end
end