#!/usr/bin/env ruby
#--
# Copyright 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 'rubygems'
require 'parseconfig'
require 'getoptlong'
require 'fileutils'
require 'logger'
require 'open4'

$log = Logger.new(STDOUT)
$log.level = Logger::INFO

def find_and_replace(file, find, replace)
  $log.debug("Replacing #{find} with #{replace} in file #{file}")
  data = File.open(file).read
  File.open(file, "w") do |f|
    data = data.gsub(find, replace)
    f.write(data)
  end
end

def insert_if_not_exist(file, find, to_insert)
  $log.debug("Checking if #{find} exists in file #{file}")
  data = File.open(file).read
  return if data.match(find)
  $log.debug("...inserting #{to_insert} in file #{file}")
  File.open(file, "w") do |f|
    f.write(data)
    f.write(to_insert)
  end
end

def run(cmd)
  $log.debug("Running command:")
  $log.debug(cmd)
  error_str = ""
  status = Open4.popen4(cmd) do |pid, stdin, stdout, stderr|
    $log.debug(stdout.read)
    error_str = stderr.read
  end
  $log.error(error_str) if (status.to_i != 0 and !error_str.empty?)
  $log.debug("Exit: #{status}")
  return status.to_i
end

def stop_mongo
  run "/usr/bin/pkill -u mongodb"

  $log.debug("Repair mongodb database if needed...")
  run "/bin/rm -f /var/lib/mongodb/mongod.lock"
  run "/bin/su -c 'mongod -f /etc/mongodb.conf --repair' --shell /bin/bash mongodb"
  $log.debug("Waiting for repair to complete")  
  while run('/bin/fgrep "dbexit: really exiting now" /var/log/mongodb/mongodb.log') != 0 do
    $log.info("...")
    sleep 2
  end
  run "/bin/rm -f /var/lib/mongodb/mongod.lock"
end

def start_mongo
  $log.debug "Initializing mongodb database"
  run "/bin/su -c 'mongod -f /etc/mongodb.conf' --shell /bin/bash mongodb"
  while run('/bin/fgrep "[initandlisten] waiting for connections" /var/log/mongodb/mongodb.log') != 0 do
    $log.info("...")
    sleep 3
  end
end

def restart_mongo
  $log.info("Restarting mongo db")
  stop_mongo
  start_mongo
end

def usage
    puts <<USAGE
== Synopsis

oo-setup-broker: Script to setup the broker and required services on this machine.
  This command must be run as root.

== List of arguments
  --eip|--external-ip <IP/PREFIX>	Sets up the VM to use a static IP on the external ethernet device. (Defaults to DHCP)
  --egw|--external-gw <IP>		Gateway for external IP (only for non-dhcp address)
  --ed |--external-device		Sets up the VM to use specified ethernet device. Default: eth0

  --iip|--internal-ip <IP/PREFIX>	Sets up the VM to use a static IP on the internal ethernet device. (Defaults to DHCP)
  --id |--internal-device		Sets up the VM to use specified ethernet device. (Defaults to same as external)

   -n  |--static-dns <IP>[,<IP>]        Comma seperated list of IP addresses to use for DNS forwarding
   -d  |--domain <Domain suffix>        Domain suffix for this cloud (Default: example.com)
       |--skip-node                     Skip node setup (default: false if openshift-broker-node package is installed)
  -?   |--help                          Print this message

USAGE
end

opts = GetoptLong.new(
    ["--external-ip",           "--eip", GetoptLong::OPTIONAL_ARGUMENT],
    ["--external-gw",           "--egw", GetoptLong::OPTIONAL_ARGUMENT],
    ["--external-device",       "--ed" , GetoptLong::OPTIONAL_ARGUMENT],

    ["--internal-ip",           "--iip", GetoptLong::OPTIONAL_ARGUMENT],
    ["--internal-gw",           "--igw", GetoptLong::OPTIONAL_ARGUMENT],
    ["--internal-device",       "--id" , GetoptLong::OPTIONAL_ARGUMENT],

    ["--static-dns",            "-n"  , GetoptLong::OPTIONAL_ARGUMENT],
    ["--skip-node",                     GetoptLong::NO_ARGUMENT],
    ["--domain",                "-d"  , GetoptLong::OPTIONAL_ARGUMENT],
    ["--debug",                         GetoptLong::NO_ARGUMENT],
    ["--help",                  "-?"  , GetoptLong::NO_ARGUMENT])

args = {}
begin
    opts.each{ |k,v| args[k]=v }
rescue GetoptLong::Error => e
    usage
    exit -100
end

if args["--help"]
  usage
  exit 0
end

if args["--debug"]
  $log.level = Logger::DEBUG
end

ext_eth_device = args["--external-device"] || "eth0"
ext_address    = args["--external-ip"]
ext_address,ext_prefix = ext_address.split("/") unless ext_address.nil?
ext_gw         = args["--external-gw"]

int_eth_device = args["--internal-device"] || ext_eth_device
int_address    = args["--internal-ip"]
int_address,int_prefix = int_address.split("/") unless int_address.nil?

node_domain =  args["--domain"] || "example.com"
dns            = args["--static-dns"]
dns_address    = dns.split(/,/) unless dns.nil?
ext_dhcp       = false
int_dhcp       = false
use_systemd    = File.exist?('/bin/systemd')
use_nm         = (use_systemd and File.exist?('/lib/systemd/system/NetworkManager.service'))

if ext_address.nil? #DHCP
  ext_address = `/sbin/ip addr show dev #{ext_eth_device} | awk '/inet / { split($2,a, "/") ; print a[1];}'`
  ext_dhcp = true
end 

if int_address.nil? #DHCP
  int_address = `/sbin/ip addr show dev #{int_eth_device} | awk '/inet / { split($2,a, "/") ; print a[1];}'`
  int_dhcp = true
end

if !ext_dhcp && (ext_address.nil? || ext_address.empty? || ext_prefix.nil? || ext_prefix.empty? || ext_gw.nil? || ext_gw.empty?)
  usage
  print "Must provide --external-ip <IP/PREFIX> and --external-gw <IP> for statically configuring external ethernet device.\n" 
  exit -1
end

if !int_dhcp && (int_eth_device.nil? || int_eth_device.empty? || int_eth_device == ext_eth_device || int_address.nil? || int_address.empty? || int_prefix.nil? || int_prefix.empty?)
  usage
  print "Must provide --internal-device <DEV> --internal-ip <IP/PREFIX> and --internal-gw <IP> for statically configuring internal ethernet device.\n"
  exit -1
end

ext_hw_address = `/sbin/ip addr show dev #{ext_eth_device} | grep 'link/ether' | awk '{ print $2 }'`
int_hw_address = `/sbin/ip addr show dev #{int_eth_device} | grep 'link/ether' | awk '{ print $2 }'`

if dns_address.nil?
  if ext_dhcp
    dns_address = `cat /var/lib/dhclient/dhclient-*#{ext_eth_device}.lease* | grep domain-name-servers | awk '{print $3}' | sort -u`.split(";\n").map{ |ips| ips.split(",") }.flatten
    dns_address.delete '127.0.0.1'
  else
    dns_address = ["8.8.8.8", "8.8.4.4"]
  end
end

if dns_address.nil? || dns_address.length == 0
  usage
  print "Error: Unable to determine DNS servers.\n"
  exit -1
end

if ext_address.nil? || ext_address.empty?
  usage
  print "Error: Unable to determine IP address of server.\n"
  exit -1
end

if args["--help"]
  usage
  exit -1
end

$log.info "Starting broker plugin setup"
$log.info "Configuring mongo auth plugin"
if not File.exist?("/etc/openshift/plugins.d/openshift-origin-auth-mongo.conf")
  FileUtils.mv "/etc/openshift/plugins.d/openshift-origin-auth-mongo.conf.example", "/etc/openshift/plugins.d/openshift-origin-auth-mongo.conf"
end

$log.info "Generating broker authentication keys"
if not File.exist?("/etc/openshift/server_priv.pem")
  run <<-EOF
/usr/bin/openssl genrsa -out /etc/openshift/server_priv.pem 2048
/usr/bin/openssl rsa -in /etc/openshift/server_priv.pem -pubout > /etc/openshift/server_pub.pem
EOF
end

$log.info "Configuring mcollective messaging plugin"
if `rpm -q qpid-cpp-server 2> /dev/null`.match(/is not installed/)
  $log.info "...installing qpid AMQP server"
  run "/usr/bin/yum install -y qpid-cpp-server"
end

$log.info "...configuring qpid server"
run <<-EOF
/sbin/restorecon -r /usr/sbin/mcollectived /var/log/mcollective.log /run/mcollective.pid

if [ ! -f /etc/qpidd.conf.orig ] ; then
  /bin/mv /etc/qpidd.conf /etc/qpidd.conf.orig
fi
/bin/cp -f /etc/qpidd.conf.orig /etc/qpidd.conf
if [[ "x`fgrep auth= /etc/qpidd.conf`" == xauth* ]] ; then
  /bin/sed -i -e 's/auth=yes/auth=no/' /etc/qpidd.conf
else
  /bin/echo "auth=no" >> /etc/qpidd.conf
fi
EOF

if not File.exist?("/etc/openshift/plugins.d/openshift-origin-msg-broker-mcollective.conf")
  FileUtils.mv "/etc/openshift/plugins.d/openshift-origin-msg-broker-mcollective.conf.example", "/etc/openshift/plugins.d/openshift-origin-msg-broker-mcollective.conf"
end

$log.info "...configuring mcollective client to use qpid"
File.open("/etc/mcollective/client.cfg","w") do |f|
  f.write <<-EOF
topicprefix = /topic/
main_collective = mcollective
collectives = mcollective
libdir = /usr/libexec/mcollective
loglevel = debug
logfile = /var/log/mcollective-client.log

# Plugins
securityprovider = psk
plugin.psk = unset
connector = qpid
plugin.qpid.host=broker.example.com
plugin.qpid.secure=false
plugin.qpid.timeout=5

# Facts
factsource = yaml
plugin.yaml = /etc/mcollective/facts.yaml
EOF
end

$log.info "...configuring mcollective server to use qpid"
File.open("/etc/mcollective/server.cfg", "w") do |f|
  f.write <<-EOF
topicprefix = /topic/
main_collective = mcollective
collectives = mcollective
libdir = /usr/libexec/mcollective
logfile = /var/log/mcollective.log
loglevel = debug
daemonize = 1 
direct_addressing = n

# Plugins
securityprovider = psk
plugin.psk = unset
connector = qpid
plugin.qpid.host=broker.example.com
plugin.qpid.secure=false
plugin.qpid.timeout=5

# Facts
factsource = yaml
plugin.yaml = /etc/mcollective/facts.yaml
EOF
end

$log.info "Configuring networking"
$log.info "...configuring external network"
File.open("/etc/sysconfig/network-scripts/ifcfg-#{ext_eth_device}","w") do |f|
  f.write "DEVICE=#{ext_eth_device}\n"
  f.write "ONBOOT=yes\n"
  f.write "HWADDR=#{ext_hw_address}\n"
if ext_dhcp
    f.write "BOOTPROTO=dhcp\n"
else
    f.write "BOOTPROTO=static\n"
    f.write "IPADDR=#{ext_address}\n"
    f.write "PREFIX=#{ext_prefix}\n"
    f.write "GATEWAY=#{ext_gw}\n"
end
  f.write "DNS1=127.0.0.1\n"
  dns_address.each_index do |idx|
    f.write "DNS#{idx+2}=#{dns_address[idx]}\n"
  end

  f.write "TYPE=Ethernet\n"
  f.write "DEFROUTE=yes\n"
  f.write "PEERDNS=no\n" if use_nm
  f.write "PEERROUTES=yes\n"
end

if int_eth_device != ext_eth_device
  $log.info "...configuring internal network"
  File.open("/etc/sysconfig/network-scripts/ifcfg-#{int_eth_device}","w") do |f|
    f.write "DEVICE=#{int_eth_device}\n"
    f.write "ONBOOT=yes\n"
    f.write "HWADDR=#{int_hw_address}\n"
    if int_dhcp
      f.write "BOOTPROTO=dhcp\n"
    else
      f.write "BOOTPROTO=static\n"
      f.write "IPADDR=#{int_address}\n"
      f.write "PREFIX=#{int_prefix}\n"
    end
  end
end

run "/sbin/chkconfig network on"

$log.info "Configuring Mongo DB"
$log.info "...setup mongo db admin users"
$log.info "......disable mongo auth"
insert_if_not_exist("/etc/mongodb.conf", /^smallfiles = .*$/, "smallfiles = true")
find_and_replace("/etc/mongodb.conf", /^#auth = .*$/, "auth = false")
restart_mongo
$log.info "......set db admin users"
run "/usr/bin/mongo localhost/openshift_broker_dev --eval 'db.addUser(\"openshift\", \"mooo\")'"
run "/usr/bin/mongo localhost/admin --eval 'db.addUser(\"openshift\", \"mooo\")'"
$log.info "......register mongo auth admin user"
run "/usr/bin/mongo openshift_broker_dev --username openshift --password mooo --eval 'db.auth_user.update({\"_id\":\"admin\"}, {\"_id\":\"admin\",\"user\":\"admin\",\"password\":\"2a8462d93a13e51387a5e607cbd1139f\"}, true)'"
stop_mongo
$log.info "......enable mongo auth"
find_and_replace("/etc/mongodb.conf", /^auth = .*$/, "auth = true")

$log.info "Configuring BIND DNS server"
$log.info "...installing DNS server related SELinux policies (This might take a while)"
run <<-EOOF
/usr/sbin/semanage -i - <<_EOF
boolean -m --on named_write_master_zones
_EOF
EOOF

unless File.exist?('/etc/rndc.key')
  $log.info  "...generating rndc.key file"
  run "/usr/sbin/rndc-confgen -a"
  run "/sbin/restorecon /etc/rndc.* /etc/named.*"
  run "/bin/chown root:named /etc/rndc.key"
  run "/bin/chmod 0640 /etc/rndc.key"
end
run "/usr/sbin/oo-setup-bind #{node_domain}"

$log.info "...updating Bind forwarders file with DNS servers #{dns_address.join(",")}"

File.open("/var/named/forwarders.conf", "w") do |f|
  f.write("forwarders { #{dns_address.join(" ; ")} ; } ;")
end
run "/sbin/restorecon -v /var/named/forwarders.conf"
run "/sbin/chkconfig named on"

$log.info "...update resolve.conf to use local dns server"
File.open("/etc/resolv.conf", "w") do |f|
  f.write("nameserver 127.0.0.1\n")
  dns_address.each { |ns| f.write("nameserver #{ns}\n") }
end

$log.info "...update network scripts to use local dns server"
File.open("/etc/sysconfig/network-scripts/ifcfg-#{ext_eth_device}","a") do |f|
  f.write "DNS1=127.0.0.1\n"
  dns_address.each_index do |idx|
    f.write "DNS#{idx+2}=#{dns_address[idx]}\n"
  end
end

$log.info "...register broker with local dns server"
run "service named restart"
run "/usr/sbin/oo-register-dns -h broker -n #{ext_address.strip} --domain #{node_domain}" 
run "service named stop"

$log.info "Updating system wide OpenShift CLI configuration to use local broker"
File.open("/etc/openshift/express.conf", "w") do |f|
  f.write("libra_server=broker.example.com\n")
end

$log.info "Updating root user's OpenShift configuration to use 'admin' user"
FileUtils.mkdir_p "/root/.openshift"
File.open("/root/.openshift/express.conf", "w") do |f|
  f.write("username=admin\n")
end

$log.info "Installing broker gem dependencies (This might take a while)"
run <<-EOF
pushd /var/www/openshift/broker/
rm -f Gemfile.lock
bundle install
chown apache:apache Gemfile.lock
popd
EOF

$log.info "Updating sshd configuration to enable OpenShift git push"
insert_if_not_exist("/etc/ssh/sshd_config", "AcceptEnv GIT_SSH", "\nAcceptEnv GIT_SSH\n")
 
$log.info "Enabling OpenShift services"
["httpd","sshd","qpidd","mcollective","openshift-broker","mongod"].each do |service|
  run "/sbin/chkconfig #{service} on" 
end

unless args["--skip-node"] || /not installed/.match(`rpm -q openshift-origin-node-util`)
  $log.info "Configuring node services"
  cmd_args = []
  cmd_args += ["--external-ip", "#{ext_address.strip}/#{ext_prefix.strip}"] unless ext_dhcp
  cmd_args += ["--external-gw", ext_gw.strip] unless ext_dhcp
  cmd_args += ["--external-device", ext_eth_device.strip] unless ext_eth_device.nil?
  cmd_args += ["--internal-ip", "#{int_address.strip}/#{int_prefix.strip}"] unless int_dhcp
  cmd_args += ["--internal-device", int_eth_device.strip] unless int_eth_device.nil?
  cmd_args += ["--static-dns", dns_address.uniq.join(",")] unless dns_address.nil?
  cmd_args += ["--domain", node_domain.strip] unless node_domain.nil?
  cmd_args += ["--debug"] if args["--debug"]

  system("/usr/sbin/oo-setup-node --with-node-hostname broker --with-broker-ip 127.0.0.1 #{cmd_args.join(' ')}")
else
  log.warn "NOTE: Please ensure that the clocks between broker and node are in sync."
  log.warn "NOTE: Please reboot this node to pick up cgroups, quota and service changes"
end
