#!/usr/bin/env oo-ruby
require 'rubygems'
require 'pp'
require 'thread'
require 'getoptlong'
require 'stringio'
require 'set'

$max_threads = 8
WORK_DIR = '/tmp/oseupgrade/migration/1'
ACTIVE_SUFFIX = '_active'

# For each of these sets:  nil implies all and empty implies none
MIGRATION_CART_TYPES = nil
RESTART_CART_TYPES = [].to_set
REDEPLOY_HTTPD_PROXY_CART_TYPES = [].to_set # Deploys on top of existing config.  Will not fixup renames.

$all_cart_types = nil
$restart_only_cart_types = nil
if MIGRATION_CART_TYPES && RESTART_CART_TYPES && REDEPLOY_HTTPD_PROXY_CART_TYPES
  $all_cart_types = Set.new
  $all_cart_types = MIGRATION_CART_TYPES + RESTART_CART_TYPES + REDEPLOY_HTTPD_PROXY_CART_TYPES

  $restart_only_cart_types = RESTART_CART_TYPES.dup
  $restart_only_cart_types -= (MIGRATION_CART_TYPES + REDEPLOY_HTTPD_PROXY_CART_TYPES)
end

STDOUT.sync, STDERR.sync = true

#
#  Migrate the specified gear
#
def migrate_gear(login, app_name, gear_uuid, number)
  total_migrate_gear_start_time = (Time.now.to_f * 1000).to_i
  migrate_cmd = "#{__FILE__} --login '#{login}' --migrate-gear '#{gear_uuid}' --app-name '#{app_name}' --number '#{number}'"
  out = StringIO.new
  out << "Migrating gear on node with: #{migrate_cmd}\n"
  begin
    user = nil
    begin
      user = CloudUser.with(consistency: :eventual).find_by(login: login)
    rescue Mongoid::Errors::DocumentNotFound
    end
    if user
      app, gear = Application.find_by_gear_uuid(gear_uuid)
      if app
        if gear
          server_identity = gear.server_identity
          # gear.node_profile = app.node_profile if gear.node_profile.nil?
          begin
            Timeout::timeout(240) do
              output = ''
              exit_code = 1
              migrate = MIGRATION_CART_TYPES.nil?
              cartridges = gear.group_instance.all_component_instances.map { |ci| ci.cartridge_name }.uniq
              MIGRATION_CART_TYPES.each do |cart_name|
                if cartridges.include?(cart_name)
                  migrate = true
                  break
                end
              end unless migrate

              if migrate
                migrate_on_node_start_time = (Time.now.to_f * 1000).to_i
                out << "Migrating on node...\n"
                OpenShift::MCollectiveApplicationContainerProxy.rpc_exec('oseupgrade', server_identity) do |client|
                  client.migrate(:uuid => gear_uuid,
                                 :namespace => app.domain.namespace,
                                 :gear_name => gear.name,
                                 :number => number) do |response|
                    exit_code = response[:body][:data][:exitcode]
                    output = response[:body][:data][:output]
                  end
                end
                migrate_on_node_time = (Time.now.to_f * 1000).to_i - migrate_on_node_start_time
                out << "***time_migrate_on_node_measured_from_broker=#{migrate_on_node_time}***\n"
              end
              if (output.length > 0)
                out << "Migrate on node output:\n #{output}\n"
              end
              if migrate && exit_code != 0
                out << "Migrate on node exit code: #{exit_code}\n"
                raise "Failed migrating gear. Rerun with: #{migrate_cmd}"
              else
                redeploy_httpd_proxy = REDEPLOY_HTTPD_PROXY_CART_TYPES.nil?

                if REDEPLOY_HTTPD_PROXY_CART_TYPES
                  gear_cartridges = cartridges
                  REDEPLOY_HTTPD_PROXY_CART_TYPES.each do |cart_name|
                    redeploy_httpd_proxy = gear_cartridges.include?(cart_name)
                    break if redeploy_httpd_proxy
                  end unless redeploy_httpd_proxy
                end

                # Should call oo-frontend-destroy and re-build the
                # application, including idle state from scratch if
                # need to purge possibly stale configuration.
                # remove_httpd_proxy(gear, out) if recreate_httpd_proxy

                redeploy_httpd_proxy_start_time = (Time.now.to_f * 1000).to_i
                redeploy_httpd_proxy(gear, out) if redeploy_httpd_proxy
                redeploy_httpd_proxy_time = (Time.now.to_f * 1000).to_i - redeploy_httpd_proxy_start_time
                out << "***time_redeploy_httpd_proxy=#{redeploy_httpd_proxy_time}***\n"
                redeploy_aliases(gear, out) if redeploy_httpd_proxy

                restart_start_time = (Time.now.to_f * 1000).to_i
                cartridges.each do |gear_cart|
                  restart = RESTART_CART_TYPES.nil?
                  RESTART_CART_TYPES.each do |cart_name|
                    if gear_cart == cart_name
                      restart = true
                      break
                    end
                  end unless restart
                  if restart
                    component = gear.group_instance.all_component_instances.to_a.find { |ci| ci.cartridge_name==gear_cart }
                    restart_component(gear, component, out)
                  end
                end
                restart_time = (Time.now.to_f * 1000).to_i - restart_start_time
                out << "***time_restart=#{restart_time}***\n"
              end
            end
          rescue Timeout::Error
            raise "Command '#{migrate_cmd}' timed out"
          end
        else
          out << "WARNING: Gear not found with uuid #{gear_uuid} for app '#{app_name}' and user '#{login}'\n"
        end
      else
        out << "WARNING: App not found: #{app_name}\n"
      end
    else
      raise "User not found: #{login}"
    end
  rescue Exception => e
    raise "#{e.message}\n#{e.backtrace}\nOutput:\n#{out.string}"
  end
  total_migrate_gear_time = (Time.now.to_f * 1000).to_i - total_migrate_gear_start_time
  out << "***time_total_migrate_gear_measured_from_broker=#{total_migrate_gear_time}***\n"
  out.string
end

def restart_component(gear, component, out)
  if component
    leave_stopped = false
    reply = gear.status(component)
    reply.properties["attributes"][gear.uuid].each do |key, value|
      if key == 'status'
        case value
        when "ALREADY_STOPPED"
          leave_stopped = true
        when "ALREADY_IDLED"
          leave_stopped = true
        end
      end
    end
    unless leave_stopped
      begin
        out << "Stopping component '#{component.cartridge_name}' on gear with uuid '#{gear.uuid}' on node '#{gear.server_identity}'\n"
        reply.append gear.stop(component.cartridge_name)
      rescue Exception => e
        out << "WARNING: Error stopping component '#{component.cartridge_name}' on gear with uuid '#{gear.uuid}' on node '#{gear.server_identity}': #{e.message}\n"
      end
      begin
        out << "Force stopping component '#{component.cartridge_name}' on gear with uuid '#{gear.uuid}' on node '#{gear.server_identity}'\n"
        reply.append gear.force_stop(component.cartridge_name)
      rescue Exception => e
        out << "WARNING: Error force stopping component '#{component.cartridge_name}' on gear with uuid '#{gear.uuid}' on node '#{gear.server_identity}': #{e.message}\n"
      end
      num_tries = 2
      (1..num_tries).each do |i|
        out << "Restarting component '#{component.cartridge_name}' on gear with uuid '#{gear.uuid}' on node '#{gear.server_identity}'\n"
        begin
          reply.append gear.restart(component.cartridge_name)
          break
        rescue Exception => e
          if i == num_tries
            out << "Failed to restart component '#{component.cartridge_name}' on gear with uuid '#{gear.uuid}' on node '#{gear.server_identity}' after #{num_tries} tries with exception: #{e.message}\n"
            out << "***acceptable_error_restart_component={\"gear_uuid\":\"#{gear.uuid}\",\"component_name\":\"#{component.cartridge_name}\",\"server_identity\":\"#{gear.server_identity}\",\"login\":\"#{gear.app.domain.owner.login}\",\"app_name\":\"#{gear.app.name}\"}***\n"
          end
        end
      end
    else
      out << "Leaving component stopped '#{component.cartridge_name}' on gear with uuid '#{gear.uuid}' on node '#{gear.server_identity}'\n"
    end
    out << "RESTART DEBUG OUTPUT:\n#{reply.debugIO.string}\n" unless reply.debugIO.string.empty?
    out << "RESTART ERROR OUTPUT:\n#{reply.errorIO.string}\n" unless reply.errorIO.string.empty?
  end
end

def redeploy_httpd_proxy(gear, out)
  httpd_proxy_action(gear, 'deploy-httpd-proxy', out)
end

def remove_httpd_proxy(gear, out)
  httpd_proxy_action(gear, 'remove-httpd-proxy', out)
end

def httpd_proxy_action(gear, action, out)
  gear_cartridges = gear.group_instance.all_component_instances.map { |ci| ci.cartridge_name }.uniq
  embedded_carts = CartridgeCache.cartridge_names('embedded')
  gear_cartridges.each do |cart|
    out << "#{action} for '#{cart}' on gear '#{gear.name}' with uuid '#{gear.uuid}' on node #{gear.server_identity}\n"
    embedded = embedded_carts.include? cart
    args = gear.get_proxy.send(:build_base_gear_args, gear)
    reply = gear.get_proxy.send(:run_cartridge_command, (embedded ? "embedded/" : '') + cart, gear, action, args, false)

    out << "DEPLOY_HTTP_PROXY DEBUG OUTPUT:\n#{reply.debugIO.string}\n" unless reply.debugIO.string.empty?
    out << "DEPLOY_HTTP_PROXY ERROR OUTPUT:\n#{reply.errorIO.string}\n" unless reply.errorIO.string.empty?

    if reply.exitcode != 0
      out << "#{action} for cart: #{cart} on node exit code: #{reply.exitcode}\n"
      raise "Failed deploying httpd proxy for gear '#{gear.name}' with uuid '#{gear.uuid}' on node '#{gear.server_identity}'"
    end
  end
end

def redeploy_aliases(gear, out)
  unless gear.app.aliases.nil?
    if gear.app_dns
      gear.app.aliases.each do |server_alias|
        out << "Adding alias '#{server_alias}' for '#{gear.name}' with uuid '#{gear.uuid}' on node #{gear.server_identity}\n"
        reply = gear.add_alias(server_alias)
        out << "ADD_ALIAS DEBUG OUTPUT:\n#{reply.debugIO.string}\n" unless reply.debugIO.string.empty?
        out << "ADD_ALIAS ERROR OUTPUT:\n#{reply.errorIO.string}\n" unless reply.errorIO.string.empty?
        if reply.exitcode != 0
          out << "Add alias on node exit code: #{reply.exitcode}\n"
          raise "Failed adding alias #{server_alias} for gear '#{gear.name}' with uuid '#{gear.uuid}' on node '#{gear.server_identity}'"
        end
      end
    end
  end
end

def add_to(stuffs, more_stuffs)
  more_stuffs.each do |topic, stuff|
    if stuffs[topic]
      stuffs[topic] += stuff
    else
      stuffs[topic] = stuff
    end
  end
end

def migrate(number, continue=false, target_server_identity=nil, migrate_position=1, num_migrators=1)
  start_time = (Time.now.to_f * 1000).to_i
  logins_cnt = 0
  gear_cnt = 0
  node_to_gears = {}

  puts "Getting all active gears..."
  gather_active_gears_start_time = (Time.now.to_f * 1000).to_i
  active_gears_map = OpenShift::ApplicationContainerProxy.get_all_active_gears
  gather_active_gears_total_time = (Time.now.to_f * 1000).to_i - gather_active_gears_start_time


  puts "Getting all logins..."
  gather_users_start_time = (Time.now.to_f * 1000).to_i
  query = {"group_instances.gears.0" => {"$exists" => true}}
  options = {:fields => [ "uuid",
              "domain_id",
              "name",
              "created_at",
              "component_instances.cartridge_name",
              "component_instances.group_instance_id",
              "group_instances._id",
              "group_instances.gears.uuid",
              "group_instances.gears.server_identity",
              "group_instances.gears.name"],
             :timeout => false}

  ret = []
  user_map = {}
  OpenShift::DataStore.find(:cloud_users, {}, {:fields => ["_id", "uuid", "login"], :timeout => false}) do |hash|
      logins_cnt += 1
      user_uuid = hash['uuid']
      user_login = hash['login']
      user_map[hash['_id'].to_s] = [user_uuid, user_login]
  end

  domain_map = {}
  OpenShift::DataStore.find(:domains, {}, {:fields => ["_id" , "owner_id"], :timeout => false}) do |hash|
    domain_map[hash['_id'].to_s] = hash['owner_id'].to_s
  end

  OpenShift::DataStore.find(:applications, query, options) do |app|
    print '.'
    user_id = domain_map[app['domain_id'].to_s]
    if user_id.nil?
      relocated_domain = Domain.where(_id: Moped::BSON::ObjectId(app['domain_id'])).first
      next if relocated_domain.nil?
      user_id = relocated_domain.owner._id.to_s
      user_uuid = user_id
      user_login = relocated_domain.owner.login
    else
      if user_map.has_key? user_id
        user_uuid,user_login = user_map[user_id]
      else
        relocated_user = CloudUser.where(_id: Moped::BSON::ObjectId(user_id)).first
        next if relocated_user.nil?
        user_uuid = relocated_user._id.to_s
        user_login = relocated_user.login
      end
    end
    group_cart_map = {}
    app['component_instances'].each do |ci|
      gid = ci['group_instance_id'].to_s
      group_cart_map[gid] = [] if not group_cart_map.has_key? gid
      group_cart_map[gid] << ci['cartridge_name']
    end

    app['group_instances'].each do |gi|
      cart_names = group_cart_map[gi['_id'].to_s]
      gi['gears'].each do |gear|
      	server_identity = gear['server_identity']
      	if (!target_server_identity || (server_identity == target_server_identity))
      	  cart_names.each do |cart_name|
      	    if $all_cart_types.nil? || $all_cart_types.include?(cart_name)
      	      node_to_gears[server_identity] = [] unless node_to_gears[server_identity]
      	      if $restart_only_cart_types.nil? || !$restart_only_cart_types.include?(cart_name) || (active_gears_map.include?(server_identity) && active_gears_map[server_identity].include?(gear['uuid']))
            		node_to_gears[server_identity] << {:server_identity => server_identity, :uuid => gear['uuid'], :name => gear['name'], :app_name => app['name'], :login => user_login}
            		break
      	      end
      	    end
      	  end
      	end
      end if cart_names
    end
  end
  gather_users_total_time = (Time.now.to_f * 1000).to_i - gather_users_start_time

  puts "\nlogins.length: #{logins_cnt.to_s}"

  position = migrate_position - 1
  migrator_position_nodes = []
  if num_migrators > 1
    server_identities = node_to_gears.keys.sort
    server_identities.each_with_index do |server_identity, index|
      if index == position
        migrator_position_nodes << server_identity
        position += num_migrators
      else
        node_to_gears.delete(server_identity)
      end
    end
  end

  active_node_queue = []
  inactive_node_queue = []
  node_to_gears.each do |server_identity, gears|
    node_to_gears[server_identity] = nil
    unless gears.empty?
      active_gears = []
      inactive_gears = []
      gears.each do |gear|
        if active_gears_map.include?(server_identity) && active_gears_map[server_identity].include?(gear[:uuid])
          active_gears << gear
        else
          inactive_gears << gear
        end
      end

      unless active_gears.empty?
        write_node_to_file(server_identity + ACTIVE_SUFFIX, active_gears, number) unless continue
        active_node_queue << {:server_identity => server_identity + ACTIVE_SUFFIX, :gears_length => active_gears.length}
      end

      unless inactive_gears.empty?
        write_node_to_file(server_identity, inactive_gears, number) unless continue
        inactive_node_queue << {:server_identity => server_identity, :gears_length => inactive_gears.length}
      end
    end
  end
  node_to_gears.clear

  # Process the largest nodes first
  active_node_queue = active_node_queue.sort_by { |node| node[:gears_length] }.reverse
  inactive_node_queue = inactive_node_queue.sort_by { |node| node[:gears_length] }.reverse
  node_queue = active_node_queue + inactive_node_queue

  puts "#####################################################"
  if !migrator_position_nodes.empty?
    puts 'Nodes this migrator is handling:'
    puts migrator_position_nodes.pretty_inspect
  end
  puts "#####################################################"

  @failures = []
  node_threads = []
  gear_cnts = []
  mutex = Mutex.new
  timings = {}
  acceptable_errors = {}
  starting_nodes = node_queue.shift($max_threads)
  starting_nodes.each_with_index do |node, index|
    server_identity = node[:server_identity]
    gear_cnts[index] = node[:gears_length]
    gear_cnt += node[:gears_length]
    node_threads << Thread.new do
      node_timings, node_acceptable_errors = migrate_node(server_identity, continue)
      add_to(timings, node_timings)
      add_to(acceptable_errors, node_acceptable_errors)
      # Get the next available node to process
      while !node_queue.empty? do
        server_identity = nil
        mutex.synchronize do
          unless node_queue.empty?
            node = node_queue.delete_at(0)
            server_identity = node[:server_identity]
            gear_cnts[index] += node[:gears_length]
            gear_cnt += node[:gears_length]
            puts "#####################################################"
            puts "Remaining node queue:"
            puts node_queue.pretty_inspect
            puts "#####################################################"
          end
        end
        if server_identity
          node_timings, node_acceptable_errors = migrate_node(server_identity, continue)
          add_to(timings, node_timings)
          add_to(acceptable_errors, node_acceptable_errors)
        end
      end
    end
  end

  node_threads.each do |t|
    t.join
  end

  total_time = (Time.now.to_f * 1000).to_i - start_time

  unless @failures.empty?
    puts "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
    puts "Failures:"
    @failures.each do |failure|
      puts failure
    end
    puts "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
    puts ""
  end

  node_queue.each do |node|
    server_identity = node[:server_identity]
    f = migrate_file_path(server_identity)
    leftover_count = `wc -l #{f}`.to_i
    if leftover_count > 0
      puts "!!!!!!!!!!WARNING!!!!!!!!!!!!!WARNING!!!!!!!!!!!!WARNING!!!!!!!!!!"
      puts "#{leftover_count} leftover gears found in migrate file: #{f}"
      puts "You can run with --continue to try again"
      puts "!!!!!!!!!!WARNING!!!!!!!!!!!!!WARNING!!!!!!!!!!!!WARNING!!!!!!!!!!"
      puts ""
    end
  end

  unless acceptable_errors.empty?
    puts "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
    puts "Acceptable Errors:"
    pp acceptable_errors
    puts "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
    puts ""
  end

  puts "#####################################################"
  puts "Summary:"
  puts "# of users: #{logins_cnt}"
  puts "# of gears: #{gear_cnt}"
  puts "# of failures: #{@failures.length}"
  puts "Gear counts per thread: #{gear_cnts.pretty_inspect}"
  puts "Nodes migrated: #{migrator_position_nodes.pretty_inspect}" if !migrator_position_nodes.empty?
  puts "Additional timings:"
  timings.each do |topic, time_in_millis|
    puts "    #{topic}=#{time_in_millis.to_f/1000}s"
  end
  puts "Time gathering users: #{gather_users_total_time.to_f/1000}s"
  puts "Time gathering active gears: #{gather_active_gears_total_time.to_f/1000}s"
  puts "Total execution time: #{total_time.to_f/1000}s"
  puts "#####################################################"
  return @failures.length > 0 ? 1 : 0
end

def write_node_to_file(server_identity, gears, number)
  f = migrate_file_path(server_identity)
  puts "Writing #{gears.length} gears for node #{server_identity} to file #{f}"
  FileUtils.mkdir_p WORK_DIR
  FileUtils.rm_f f
  FileUtils.touch f
  gears.each_with_index do |gear, index|
    migrate_on_node_args = "#{gear[:server_identity]},#{gear[:uuid]},#{gear[:name]},#{gear[:app_name]},#{gear[:login]},#{number}"
    append_to_file(f, migrate_on_node_args)
  end
end

def error_file_path(server_identity)
  "#{WORK_DIR}/migrate_errors_#{server_identity}"
end

def log_file_path(server_identity)
  "#{WORK_DIR}/migrate_log_#{server_identity}"
end

def migrate_file_path(server_identity)
  "#{WORK_DIR}/migrate_#{server_identity}"
end

def migrate_node(server_identity, continue)
  puts "Migrating gears on node #{server_identity}"
  error_file = error_file_path(server_identity)
  FileUtils.rm_f error_file unless continue
  FileUtils.touch error_file
  log_file = log_file_path(server_identity)
  FileUtils.rm_f log_file unless continue
  FileUtils.touch log_file
  f = migrate_file_path(server_identity)
  migrate_node_cmd = "#{__FILE__} --migrate-file '#{f}'"
  output, exit_code = execute_script(migrate_node_cmd)
  puts output
  file = File.open(error_file, "r")
  begin
    while (line = file.readline)
      @failures << line.chomp
    end
  rescue EOFError
    file.close
  end
  file = File.open(log_file, "r")
  timings = {}
  acceptable_errors = {}
  begin
    while (line = file.readline)
      if line =~ /\*\*\*time_(.*)=(\d+)\*\*\*/
        timings[$1] = 0 unless timings[$1]
        timings[$1] += $2.to_i
      elsif line =~ /\*\*\*acceptable_error_(.*)=(.+)\*\*\*/
        acceptable_errors[$1] = [] unless acceptable_errors[$1]
        acceptable_errors[$1] << $2
      end
      print line
    end
  rescue EOFError
    file.close
  end
  return timings, acceptable_errors
end

def migrate_from_file(file)
  while true
    line = File.open(file, &:gets)
    if line && !line.empty?
      params = line.chomp.split(',')
      server_identity = params[0]
      gear_uuid = params[1]
      gear_name = params[2]
      app_name = params[3]
      login = params[4]
      number = params[5]
      migrate_on_node_cmd = "#{__FILE__} --login '#{login}' --migrate-gear '#{gear_uuid}' --app-name '#{app_name}' --number '#{number}'"
      base_path = server_identity
      if File.basename(file) == File.basename(migrate_file_path(server_identity) + ACTIVE_SUFFIX)
        base_path += ACTIVE_SUFFIX
      end
      error_file = error_file_path(base_path)
      log_file = log_file_path(base_path)
      append_to_file(log_file,  "Migrating app '#{app_name}' gear '#{gear_name}' with uuid '#{gear_uuid}' on node '#{server_identity}' for user: #{login}")
      num_tries = 2
      (1..num_tries).each do |i|
        begin
          output = migrate_gear(login, app_name, gear_uuid, number)
          append_to_file(log_file, output)
          break
        rescue Exception => e
          if i == num_tries
            append_to_file(log_file, "Failed to migrate with cmd: '#{migrate_on_node_cmd}' after #{num_tries} tries with exception: #{e.message}")
            append_to_file(error_file, migrate_on_node_cmd)
            break
          else
            user = nil
            begin
              user = CloudUser.with(consistency: :eventual).find_by(login: login)
            rescue Mongoid::Errors::DocumentNotFound
            end
            if user && Application.find(user, app_name)
              sleep 4
            else
              append_to_file(log_file, "App '#{app_name}' no longer found in datastore with uuid '#{gear_uuid}'.  Ignoring...")
              break
            end
          end
        end
      end
      `sed -i '1,1d' #{file}`
    else
      break
    end
  end
end

def self.append_to_file(f, value)
  file = File.open(f, 'a')
  begin
    file.puts value
  ensure
    file.close
  end
end

def execute_script(cmd, num_tries=1, timeout=28800)
  exitcode = nil
  output = ''
  (1..num_tries).each do |i|
    pid = nil
    begin
      Timeout::timeout(timeout) do
        read, write = IO.pipe
        pid = fork {
          # child
          $stdout.reopen write
          read.close
          exec(cmd)
        }
        # parent
        write.close
        read.each do |line|
          output << line
        end
        Process.waitpid(pid)
        exitcode = $?.exitstatus
      end
      break
    rescue Timeout::Error
      begin
        Process.kill("TERM", pid) if pid
      rescue Exception => e
        puts "execute_script: WARNING - Failed to kill cmd: '#{cmd}' with message: #{e.message}"
      end
      puts "Command '#{cmd}' timed out"
      raise if i == num_tries
    end
  end
  return output, exitcode
end

def p_usage
  puts <<USAGE

Usage: #{$0}

  --login login_name                   User login
  --migrate-gear gear_uuid             Gear uuid of the single gear to migrate
  --app-name app_name                  App name of the gear to migrate
  --migrate-node server_identity       Server identity of the node to migrate
  --migrate-file file                  File containing the gears to migrate
  --num-migrators num                  The total number of migrators to be run.  Each migrate-position will be a
                                       migrate-position of num-migrators.  All positions must to taken to migrate
                                       all gears.  Ex: If you are going to run 2 migrators you would need to run:
                                       ./migrate --number <number> --position 1 --num-migrators 2
                                       ./migrate --number <number> --position 2 --num-migrators 2
  --migrate-position position          Postion of this migrator (1 based) amongst the num of migrators (--num_migrators)
  --max-threads num                    Indicates the number of processing queues
  --continue                           Flag indicating to continue a previous migration

USAGE
  exit 255
end

begin
  opts = GetoptLong.new(
    ["--login", GetoptLong::REQUIRED_ARGUMENT],
    ["--migrate-gear", GetoptLong::REQUIRED_ARGUMENT],
    ["--app-name", GetoptLong::REQUIRED_ARGUMENT],
    ["--migrate-node", GetoptLong::REQUIRED_ARGUMENT],
    ["--migrate-file", GetoptLong::REQUIRED_ARGUMENT],
    ["--number", GetoptLong::REQUIRED_ARGUMENT],
    ["--num-migrators", GetoptLong::REQUIRED_ARGUMENT],
    ["--migrate-position", GetoptLong::REQUIRED_ARGUMENT],
    ["--max-threads", GetoptLong::REQUIRED_ARGUMENT],
    ["--continue", GetoptLong::NO_ARGUMENT],
    ["--help", "-h", GetoptLong::NO_ARGUMENT]
  )
  opt = {}
  opts.each do |o, a|
    opt[o[2..-1]] = a.to_s
  end
rescue Exception => e
  p_usage
end

if opt['help']
  p_usage
end

####################################### execute ##########################################
#

# load broker app environment
$:.unshift('/var/www/openshift/broker')
require 'config/environment'
# Disable analytics for admin scripts
Rails.configuration.analytics[:enabled] = false
# set the mco timeout for discovering nodes
Rails.configuration.msg_broker[:rpc_options][:disctimeout] = 20

if opt['max-threads']
  max_threads = opt['max-threads'].to_i
  if max_threads < 50 && max_threads > 0
    $max_threads = max_threads
  else
    puts "max-threads must be less than 50 and greater than 0"
    exit 255
  end
end

if opt['migrate-file']
  migrate_from_file(opt['migrate-file'])
elsif opt['migrate-gear']
  if opt['login'] && opt['app-name'] && opt['number']
    puts migrate_gear(opt['login'], opt['app-name'], opt['migrate-gear'], opt['number'])
  else
    puts "--login, --app-name, and --number is required with --migrate-gear"
    exit 255
  end
elsif opt['migrate-node']
  if opt['number']
    migrate_file = migrate_file_path(opt['migrate-node'])
    if opt['continue']
      migrate(opt['number'], true, opt['migrate-node'])
    elsif File.exists?(migrate_file)
        puts <<-WARNING
!!!!!!!!!!!!!!!!!!!! EXISTING MIGRATION DATA FOUND !!!!!!!!!!!!!!!!!!!!
Data from a previous migration exists at #{migrate_file}.  You must
either move/remove (Ex: rm #{migrate_file}) that data or pick up
where it left off with '#{__FILE__} --migrate-node #{opt['migrate-node']} --number '#{opt['number']}' --continue'.
WARNING
        exit 1
    else
      migrate(opt['number'], false, opt['migrate-node'])
    end
  else
    puts "--number is required with --migrate-node"
    exit 255
  end
else
  if opt['number']
    num_migrators = opt['num-migrators']
    migrate_position = opt['migrate-position']
    if num_migrators || migrate_position
      unless num_migrators
        puts "--num-migrators is required with --migrate-position"
        exit 255
      end
      unless migrate_position
        puts "--migrate-position is required with --num-migrators"
        exit 255
      end
      num_migrators = num_migrators.to_i
      migrate_position = migrate_position.to_i
      unless migrate_position > 0 && migrate_position <= num_migrators
        puts "migrate-position must be > 0 and <= num_migrators"
        exit 255
      end
      unless num_migrators > 0
        puts "num_migrators must be > 0"
        exit 255
      end
    else
      num_migrators = 1
      migrate_position = 1
    end
    if opt['continue']
      rc = migrate(opt['number'], true, nil, migrate_position, num_migrators)
      exit rc
    elsif File.exists?(WORK_DIR)
      puts <<-WARNING
!!!!!!!!!!!!!!!!!!!! EXISTING MIGRATION DATA FOUND !!!!!!!!!!!!!!!!!!!!
Data from a previous migration exists at #{WORK_DIR}.  You must
either move/remove (Ex: rm -rf #{WORK_DIR}) that data or pick up
where it left off with '#{__FILE__} --continue'.
WARNING
      exit 1
    else
      rc = migrate(opt['number'], false, nil, migrate_position, num_migrators)
      exit rc
    end
  else
    puts "--number is required"
    exit 255
  end
end
