#!/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"

$count = 0
def clean_app(a)
  $count += 1
  delete_app = false
  dlist = []
  a.pending_op_groups.each { |op| 
    if op.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
      Application.run_in_application_lock(a) do
        begin
          if op.pending_ops.where(:state => :rolledback).count > 0
            raise Exception.new("Op already being rolled back.. continuing.")
          end
          op.execute 
          a.unreserve_gears(op.num_gears_removed)
          success = true
          delete_app = true if op.op_type == :delete_app
        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
            a.unreserve_gears(num_gears_recovered)
            success = true
            puts " complete."
          rescue Exception=>ex
            success = false
            puts " failed again!"
          end
        end
        dlist << op if success
      end
      break if !success
    end
  } 
  dlist.each { |op| op.delete }
  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.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
  dlist = d.pending_ops.select { |op| 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.with(consistency: :strong).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
  dlist = u.pending_ops.select { |op| op.op_type.nil? or op.pending_domains.empty? }
  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.with(consistency: :strong).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(:uuid => 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
