#!/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 'rubygems'
require 'getoptlong'
require 'time'

def usage
  puts <<USAGE
== Synopsis

#{$0}: Check user applications and delete all pending_op_groups that were created before a given time.

User operations (start/stop/add cart/etc.) are queued per-app; occasionally one may get stuck, unable
to operate and holding up the queue so that no other operations can proceed on that application.
This tool removes stale operations from the application queue.

== Usage

#{$0} OPTIONS

Options:
-t|--time
    Age in hours (default 1) to delete; pending ops older than this will be deleted.
-u|--uuid
    Specific app's uuid where the pruning operation will be done - if not given all apps, domains and users will be screened
-h|--help
    Show Usage info
USAGE
  exit 255
end

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

usage if args["--help"]

require "/var/www/openshift/broker/config/environment"
 
# Reset the logging options. We want DEBUG level stuff.
module Broker
  class Application < Rails::Application
    Mongoid.logger = Rails.logger
    Moped.logger = Rails.logger
    Mongoid.logger.level = Logger::DEBUG
    Moped.logger.level = Logger::DEBUG
  end
end

$count = 0
def clean_app(a)
  $count += 1
  delete_app = false
  dlist = []
  Application.run_in_application_lock(a) do
    a.reload
    a.pending_op_groups.each { |op| 
      if op._type.nil? 
        puts "ERROR in cleaning up application's op because the _type is nil. App uuid - #{a.uuid}. Op - #{op.inspect}"
        next
      end
      puts "WARNING : Application #{a.uuid} has created_at field nil for op - #{op.inspect}" if op.created_at.nil?

      if op.created_at.nil? or op.created_at < $t 
        puts
        puts "Executing op for app (#{a.uuid}) - #{op.inspect} "
        success = false
        begin
          if op.pending_ops.where(:state => :rolledback).count > 0
            raise Exception.new("Op already being rolled back.. continuing.")
          end
          op.execute 
          op.unreserve_gears(op.num_gears_removed, a)
          success = true
          delete_app = true if (op.class == DeleteAppOpGroup) or (op.class == RemoveFeaturesOpGroup and op.remove_all_features)
        rescue Exception => e
          print "Execution failed. Rolling back.."
          begin
            op.execute_rollback
            num_gears_recovered = op.num_gears_added - op.num_gears_created + op.num_gears_rolled_back + op.num_gears_destroyed
            op.unreserve_gears(num_gears_recovered, a)
            success = true
            puts " complete."
          rescue Exception=>ex
            success = false
            puts " failed again!"
          end
        end
        dlist << op if success
      end
      break if !success
    } 
    dlist.each { |op| op.delete }
  end # end of lock
  if not delete_app
    a.save
    a.pending_op_groups.each { |op|
      if op.created_at.nil? or op.created_at < $t 
        next if op._type.nil?
        puts
        puts "Failed to clear op for app (#{a.uuid}) - #{op.inspect} "
      end
    } 
    if (a.group_instances.nil? or a.group_instances.empty?) and (a.component_instances.nil? or a.component_instances.empty?)
      a.delete
    end
  end
end

$domain_count = 0
def clean_domain(d)
  $domain_count += 1
  d.reload
  dlist = d.pending_ops.select { |op| op._type.nil? }
  dlist.each { |op| op.delete }
  d.pending_ops.delete_if { |op| op.nil? }
  d.pending_ops.each { |op| op.state = :init if op.state==:queued and (op.created_at.nil? or op.created_at < $t) }
  d.save
  d.run_jobs rescue nil
  d.reload
  dlist = d.pending_ops.select { |op| (op.created_at.nil? or op.created_at < $t) and op.completed? }
  dlist.each { |op| op.delete }
  d.pending_ops.each { |op|
    puts "WARNING : Domain #{d.namespace} has created_at field nil for op - #{op.inspect}" if op.created_at.nil?
    if (op.created_at.nil? or op.created_at < $t) and not op.completed?
      puts "Failed to clear op for domain (#{d.namespace}) - #{op.inspect} "
      op.state = :queued if op.state == :init
    end
  }
  d.save
end

$user_count = 0
def clean_user(u)
  $user_count += 1
  u.reload
  dlist = u.pending_ops.select { |op| op._type.nil? }
  dlist.each { |op| op.delete }
  u.pending_ops.delete_if { |op| op.nil? }
  u.pending_ops.each { |op| op.state = :init if op.state==:queued and (op.created_at.nil? or op.created_at < $t) }
  u.save
  u.run_jobs rescue nil
  u.reload
  dlist = u.pending_ops.select { |op| (op.created_at.nil? or op.created_at < $t) and op.completed? }
  dlist.each { |op| puts "Clearing op for user (#{u.login}) - #{op.inspect} " and op.delete }
  u.pending_ops.each { |op|
    puts "WARNING : User #{u.login} has created_at field nil for op - #{op.inspect}" if op.created_at.nil?
    if (op.created_at.nil? or op.created_at < $t) and not op.completed?
      puts "Failed to clear op for user (#{u.login}) - #{op.inspect} "
      # putting back to queued state if it is in init state
      op.state = :queued if op.state == :init
    end
  }
  u.save
end

hours = args["--time"] || 1
hours = hours.to_i
$t = Time.now - hours*60*60
uuid = args["--uuid"]

if uuid.nil?
  Application.no_timeout.lt("pending_op_groups.created_at" => $t).each { |a| 
    begin
      clean_app(a) 
    rescue Exception=>e
      puts e.message
      puts e.backtrace
    end
  }
  Domain.no_timeout.lt("pending_ops.created_at" => $t).each { |d| 
    begin
      clean_domain(d)
    rescue Exception=>e
      puts e.message
      puts e.backtrace
    end
  }
  CloudUser.no_timeout.lt("pending_ops.created_at" => $t).each { |u| 
    begin
      clean_user(u)
    rescue Exception=>e
      puts e.message
      puts e.backtrace
    end
  }
else
  Application.where("_id" => uuid).lt("pending_op_groups.created_at" => $t).each { |a| clean_app(a) }
end

puts "#{$count} applications were cleaned up. #{$user_count} users were cleaned up. #{$domain_count} domains were cleaned up."
exit 0
