#!/usr/bin/ruby

# wallaby-agent:  the wallaby store
#
# Copyright (c) 2009--2010 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 'spqr/spqr'
require 'spqr/app'

require 'mrg/grid/config'
require 'mrg/grid/util/daemon'

require 'logger'
require 'syslog'
require 'optparse'

require 'pathname'

# resolves symbolic links and "."/".." in a path that *mostly* exists,
# where a "mostly-existing" path is defined as one in which at most
# the rightmost component does not exist.
def path_resolve(p)
  path = Pathname.new(p)

  # we can realpath existing files
  if path.exist?
    return path.realpath.to_s
  end

  # we're creating a new file here.  the dir must exist, but the file will not
  dirname = path.dirname.realpath rescue (raise "Directory #{path.dirname.to_s} does not exist")
  (dirname + path.basename).to_s
end

include ::Mrg::Grid::Util::Daemon

USE_PREPARED_STATEMENTS = ENV['WALLABY_USE_PREPARED_STATEMENTS'] && (ENV['WALLABY_USE_PREPARED_STATEMENTS'] == "1")

dbname = ENV['WALLABY_CONFIGDB_NAME'] || ":memory:"
snapdb = ENV['WALLABY_SNAPDB_NAME'] || ":memory:"
host = ENV['WALLABY_BROKER_HOST'] || "localhost"
port = (ENV['WALLABY_BROKER_PORT'] || 5672).to_i
username = ENV['WALLABY_BROKER_USER']
password = ENV['WALLABY_BROKER_PASSWORD']
explicit_mechanism = ENV['WALLABY_BROKER_MECHANISM']
logfile = ENV['WALLABY_LOGFILE']
debug = (ENV['WALLABY_LOGLEVEL'] && Logger.const_get(ENV['WALLABY_LOGLEVEL'].upcase)) || Logger::WARN
do_daemonify = !ENV['WALLABY_FOREGROUND']
run_as = nil
logoptions = nil

op = OptionParser.new do |opts|
  opts.banner = "Usage wallaby-agent [options]"
  
  opts.on("-l", "--logfile FILE", "file for wallaby-agent log") do |file|
    logfile = path_resolve(file)
  end
  
  opts.on("-d", "--dbname FILE", "file for persistent storage (will be created if it doesn't exist)") do |db| 
    dbname = path_resolve(db)
  end
  
  opts.on("-s", "--snapdb FILE", "file for store snapshots (will be created if it doesn't exist)") do |db|
    snapdb = path_resolve(db)
  end
  
  opts.on("-h", "--help", "shows this message") do
    raise OptionParser::InvalidOption.new
  end

  opts.on("-H", "--host HOSTNAME", "qpid broker host (default localhost)") do |h|
    host = h
  end
  
  opts.on("-p", "--port NUM", "qpid broker port (default 5672)") do |num|
    port = num.to_i
  end
  
  opts.on("-U", "--user NAME", "qpid username") do |name|
    username = name
  end
  
  opts.on("-P", "--password PASS", "qpid password") do |pass|
    password = pass
  end

  opts.on("-M", "--auth-mechanism PASS", SPQR::App::VALID_MECHANISMS, "authentication mechanism (#{SPQR::App::VALID_MECHANISMS.join(", ")})") do |mechanism|
    explicit_mechanism = mechanism
  end
  
  opts.on("--enable-skeleton-group", "enable the \"skeleton group\" "  "  that is copied to each newly-created node") do
    Mrg::Grid::Config::Store.quiesce(:ENABLE_SKELETON_GROUP, true)
  end

  opts.on("--run-as USER", "unix user to execute wallaby-agent as") do |user|
    # NB:  Perhaps obviously, this only has an effect if we're running as root
    # Also, if we're running in the foreground, we'll run as the current user
    # unless a run-as user is explicitly specified
    run_as = user
  end
  
  opts.on("-v", "--verbose", "output verbose debugging info" "  (repeat for more verbosity)") do
    debug = debug - 1 if debug > 0
  end

  opts.on("-f", "--foreground", "run in the foreground") do
    do_daemonify = false
  end
end

begin
  op.parse!
rescue OptionParser::InvalidOption
  puts op
  exit
end

daemonify if do_daemonify
drop_privs(run_as) if (do_daemonify || run_as)

Syslog.open do |s| 
  s.notice "storing configuration to #{dbname}"
  s.notice "storing snapshots to #{snapdb}"
  puts "storing results to #{dbname}"
  puts "storing snapshots to #{snapdb}"
end

DO_CREATE = (dbname == ":memory:" or not File.exist?(dbname))
DO_SNAPCREATE = (snapdb == ":memory:" or not File.exist?(snapdb))

begin
  Rhubarb::Persistence::open(dbname,:default,USE_PREPARED_STATEMENTS)
  Rhubarb::Persistence::open(snapdb,:snapshot,USE_PREPARED_STATEMENTS)
  
  Mrg::Grid::Config::MAIN_DB_TABLES.each {|tab| tab.db = Rhubarb::Persistence::dbs[:default] }
  Mrg::Grid::Config::SNAP_DB_TABLES.each {|tab| tab.db = Rhubarb::Persistence::dbs[:snapshot] }
  
  if DO_CREATE
    classes = Mrg::Grid::Config::MAIN_DB_TABLES
    classes.each do |cl| 
      Syslog.open do |s| 
        s.notice "creating table for #{cl.name}..."
        puts "creating table for #{cl.name}..." unless do_daemonify
      end
      cl.create_table
    end
    Rhubarb::Persistence::db.execute("PRAGMA user_version = #{Mrg::Grid::Config::DBVERSION}")
    Mrg::Grid::Config::Store.find_by_id(0).storeinit
  end
  
  if DO_SNAPCREATE
    puts "creating snapshot tables"
    Mrg::Grid::Config::SNAP_DB_TABLES.each {|cl| cl.create_table(:snapshot)}
  end
  
  options = {}
  options[:loglevel] = debug
  options[:logfile] = logfile if logfile
  options[:appname] = "com.redhat.grid.config:Store"
  options[:user] = username if username
  options[:password] = password if password
  options[:server] = host
  options[:port] = port
  options[:mechanism] = explicit_mechanism if explicit_mechanism

  app = SPQR::App.new(options)
  app.register Mrg::Grid::Config::Store,Mrg::Grid::Config::Node,Mrg::Grid::Config::ConfigVersion,Mrg::Grid::Config::Feature,Mrg::Grid::Config::Group,Mrg::Grid::Config::Parameter,Mrg::Grid::Config::Subsystem,Mrg::Grid::Config::Snapshot,Mrg::Grid::Config::NodeUpdatedNotice,Mrg::Grid::Config::ConfigVersion
  $wallaby_log = Mrg::Grid::Config::Store.log
  
  begin
    Mrg::Grid::Config::Store.find_by_id(0).storeinit("remove-spurious"=>"yes")
    [:default, :snapshot].each do |which|
      Rhubarb::Persistence::dbs[which].execute("vacuum")
    end
  rescue Exception => ex
    s.warn "exception encountered during routine cleanup:  #{ex.inspect}"
    puts "exception encountered during routine cleanup:  #{ex.inspect}" unless do_daemonify
  end
  
  app.main
rescue Exception => ex
  Syslog.open do |s|
    s.crit "agent exiting with exception #{ex.inspect}"
    puts "agent exiting with exception #{ex.inspect}" unless do_daemonify
  end
end
