#!/usr/bin/env oo-ruby

#--
# Copyright 2012 Red Hat, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#++

require "#{ENV['OPENSHIFT_BROKER_DIR'] || '/var/www/openshift/broker'}/config/environment"
require 'getoptlong'
require 'time'
include AdminHelper

# Disable analytics for admin scripts
Rails.configuration.analytics[:enabled] = false
Rails.configuration.msg_broker[:rpc_options][:disctimeout] = 20
Rails.configuration.msg_broker[:rpc_options][:timeout] = 600

trap("INT") do
  print_message "#{$0} Interrupted", true
  exit 1
end

def usage_additional_plugins
  usage_info = ""
  if $billing_enabled
    billing_api = OpenShift::BillingService.instance
    usage_info += billing_api.display_check_help.to_s
  end
  usage_info
end

def usage
  puts <<USAGE
== Synopsis

#{$0}: Check all user applications

== Usage

#{$0} OPTIONS

Options:
-v|--verbose
    Print information about each check being performed
-l|--level [0, 1]
    Level '0' is default, and checks to see if any gears are present in mongo but don't exist on the nodes and vice-versa.
    Level '1' performs checks for consumed_gears count mismatches, application data integrity in mongo, and checks for unused and unreserved gear UIDs
    #{usage_additional_plugins}
-h|--help
    Show Usage info
USAGE
  exit 255
end

args = {}
begin
  opts = GetoptLong.new(
    ["--verbose",          "-v", GetoptLong::NO_ARGUMENT],
    ["--level",            "-l", GetoptLong::REQUIRED_ARGUMENT],
    ["--help",             "-h", GetoptLong::NO_ARGUMENT]
  )
  opts.each{ |k,v| args[k]=v }
rescue GetoptLong::Error => e
  usage
end

level = args["--level"].to_i || 0
$verbose = args["--verbose"]
usage if args["--help"]

def datastore_has_gear?(gear_uuid, app_id=nil)
  # specifying the app_id first if present, this makes better use of the indexes and performance is better
  if app_id
    query = { "_id" => app_id }
  else
    query = {}
  end
  query['gears.uuid'] = gear_uuid
  return Application.where(query).exists?
end

$chk_gear_mongo_node = true
if level >= 1
  $chk_app = true
  $chk_district = true
  $chk_usage = true
end

current_time = Time.now.utc
puts "Started at: #{current_time}\n\n"
start_time = (current_time.to_f * 1000).to_i

populate_user_hash
populate_domain_hash
populate_district_hash
populate_app_hash
populate_usage_hash

node_hash, gear_hash = OpenShift::ApplicationContainerProxy.get_all_gears
puts "Total gears found on the nodes: #{node_hash.length}"
puts "Total nodes that responded: #{gear_hash.length}"

non_responding_nodes = {}
current_time = Time.now.utc
puts "Checking application gears on corresponding nodes" if $verbose
$datastore_hash.each do |gear_uuid, gear_info|
  creation_time = gear_info['creation_time'].utc
  server_identity = gear_info['server_identity']
  app_id = gear_info['app_id']

  if (current_time - creation_time) > 600
    if !node_hash.has_key? gear_uuid
      gear_si_present = (gear_hash.has_key?server_identity)
      datastore_si_present = (datastore_has_gear?(gear_uuid, app_id))
      if gear_si_present and datastore_si_present
          puts "#{gear_uuid}...FAIL" if $verbose
          print_message "Gear #{gear_uuid} does not exist on any node"
      elsif datastore_si_present
        puts "#{gear_uuid}...FAIL" if $verbose
        non_responding_nodes[server_identity] = 0 unless non_responding_nodes.has_key? server_identity
        non_responding_nodes[server_identity] += 1
      end
    elsif server_identity != node_hash[gear_uuid][0]
      puts "#{gear_uuid}...FAIL" if $verbose
      print_message "Gear #{gear_uuid} exists but node '#{node_hash[gear_uuid][0]}' does not match DB server_identity '#{server_identity}'"
    end
  end
end

# print error messages for any non-responding nodes
non_responding_nodes.each do |server_identity, gear_count|
  print_message "The node #{server_identity} expected to contain #{gear_count} gears wasn't returned from mcollective for the gear list"
end

puts "Checking node gears in application database" if $verbose
node_hash.each do |gear_uuid, gear_info|
  datastore_gear_info = $datastore_hash[gear_uuid]
  if !datastore_gear_info
    if !datastore_has_gear?(gear_uuid)
      puts "#{gear_uuid}...FAIL" if $verbose
      print_message "Gear #{gear_uuid} exists on node #{gear_info[0]} (uid: #{gear_info[1]}) but does not exist in mongo database"
    end
  elsif !datastore_gear_info['gear_uid'].nil?
    begin
      uid = gear_info[1]
      if uid != datastore_gear_info['gear_uid'].to_i
        print_message "Gear #{gear_uuid} is using uid: '#{uid}' but has reserved uid: '#{datastore_gear_info['gear_uid'].to_i}'"
      end
    rescue Exception => e
      print_message "Failed to check gear: '#{gear_uuid}'s uid because of exception: #{e.message}"
    end
  end
end

if level >= 1
  run :find_consumed_gears_inconsistencies
  run :find_ssh_key_inconsistencies
  run :find_stale_sshkeys_and_envvars
  run :find_district_inconsistencies

  run :find_app_gear_usage_record_inconsistencies
  run :find_app_storage_usage_record_inconsistencies
  run :find_app_premium_cart_usage_record_inconsistencies
  run :find_usage_record_usage_inconsistencies
  run :find_user_plan_inconsistencies 
end

end_time = Time.now.utc
puts "\nFinished at: #{end_time}"
total_time = (end_time.to_f * 1000).to_i - start_time
puts "Total time: #{total_time.to_f/1000}s"
if $total_errors == 0
  print_message "SUCCESS", true
  errcode = 0
else
  print_message "FAILED", true
  puts "Please refer to the oo-admin-repair tool to resolve some of these inconsistencies."
  errcode = 1
end
exit errcode
