#!/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 all user applications and delete all pending_op_groups that were created before a given time

== Usage

#{$0} OPTIONS

Options:
-t|--time
    Time in hours where the pending ops older than this age will get deleted (default 1)
-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
