Default Route Ping and Failover Script [Solaris/Ruby]

Wrote this for my squid boxes. They’re built with lots of redundancy – I can fail over to a different NIC/IP address in the event of a connectivity failure.

Written and tested on Solaris, runs as a Ruby daemon.

#!/usr/local/bin/ruby
 
require 'net/smtp'
require 'daemons'
 
#
# defaultroute_failover.rb runs as a daemon.
#
#  $ ruby myproc_control.rb start
#      (myproc.rb is now running in the background)
#  $ ruby myproc_control.rb restart
#      (...)
#  $ ruby myproc_control.rb stop
#
# For testing purposes you can even run myproc.rb without forking in the background:
#
#  $ ruby myproc_control.rb run
#
 
###########################
# LOCAL CONFIGS
#
router_a = "161.201.128.1"
router_b = "62.50.202.65"
 
admin_email="noc@foo.com"
sleep_interval=60
debug=false
mail_enabled=true
#
###########################
 
hostname = `hostname`.chomp
 
active_defaultrouter = ""
backup_defaultrouter = ""
 
def get_defaultrouter
  return `netstat -rn | grep '^default' | awk '{ print $2 }'`.chomp
end
 
def ping(host)
  system "/usr/local/sbin/fping -r 5 -t 1000 -p 3000 -q #{host} >/dev/null 2>&1"
 
  if ($?.exitstatus == 0)
    return true
  else 
    return false
  end
end
 
def mail_admin(msg)
  if mail_enabled
    from = "root@#{hostname}.foo.com"
    to = admin_email
    Net::SMTP.start('localhost') do |smtp|
      smtp.send_message msg, from, to
    end
  end
end
 
def switch_defaultrouter
  `route delete default #{active_defaultrouter}`
  `route add default #{backup_defaultrouter}`
end
 
#############################################################################################################################
 
Daemons.run_proc('defaultroute_failover.rb') do
  loop do
 
    active_defaultrouter = get_defaultrouter()
 
    if active_defaultrouter == router_a
      backup_defaultrouter = router_b
    elsif active_defaultrouter == router_b
      backup_defaultrouter = router_a
    else
      msg = "Active default router is an unrecognized IP address: #{active_defaultrouter}. Exiting with no actions."
      puts msg if debug
      mail_admin msg
      exit 1
    end
 
    if debug
      puts "router_a = #{router_a}"
      puts "router_b = #{router_b}"
      puts "active_defaultrouter = #{active_defaultrouter}"
      puts "backup_defaultrouter = #{backup_defaultrouter}"
    end
 
    if ! ping(active_defaultrouter)
      puts "I canont ping the active default router at #{active_defaultrouter}!!!" if debug
      if ping(backup_defaultrouter)
        puts "I can ping the backup default router at #{backup_defaultrouter}." if debug
        switch_defaultrouter if !debug
        msg = "#{hostname} proxy defaultrouter unpingable. switched defaultrouter from #{active_defaultrouter} to #{backup_defaultrouter}."
        puts msg if debug
        mail_admin msg
      else
        puts "I can NOT ping the backup default router at #{backup_defaultrouter}!!!" if debug
        msg = "#{hostname} proxy defaultrouter and backup defaultrouter unpingable."
        puts msg if debug
        mail_admin msg
      end
    else
      puts "I can ping the active default router at #{active_defaultrouter} with no issues." if debug
    end
    sleep sleep_interval
  end
end
Posted in Uncategorized | Tagged , , | Leave a comment

Oracle Database Create Script, In Ruby

You can find this script anywhere written in bash. I decided to bang one out in Ruby. Most importantly, it’ll show a way to invoke sqlplus and pass in SQL as an argument.

sid.conf

###############################################################################################
$oracle_version="11.1.0"
 
$sid="orcl"
 
$oracle_base="/oracle/apps/oracle/oracle"
$oracle_home="#{$oracle_base}/product/11.1.0/db_1"
 
# datafile location
$data_home="#{$oracle_base}/oradata/#{$sid}"
 
# pfile location
$pfile_home="#{$oracle_base}/admin/#{$sid}/pfile"
 
# change default for production
$ctl1="#{$data_home}/control1.ctl"
$ctl2="/opt/oracle/oradata/#{$sid}/control2.ctl"
$ctl3="/backup/oracle/oradata/#{$sid}/control3.ctl"
 
# change default for production
$redo1="#{$data_home}/redo01.log"
$redo2="#{$data_home}/redo02.log"
$redo3="#{$data_home}/redo03.log"
 
$password="xxxxxxx"
###############################################################################################

create_db.rb

#!/usr/bin/ruby
 
=begin
 
create database script.
 
todo: autocreate oratab entry
 
=end
 
#
# invoke sqlplus as sysdba, run sql, return output.
#
def sqlplus(sql)
  puts sql
  return if $debug
  connect_string = %Q("/as sysdba")
  sqlplus = "#{$oracle_home}/bin/sqlplus -s #{connect_string}"
  output = ""
 
  headers= <<txt
set pages 0
set lines 300
set head off
set echo off
set verify off
set feedback off
txt
 
  IO.popen(sqlplus, "w+") do |pipe|
    pipe.puts sql
    pipe.close_write
    output = pipe.read
  end
  puts output
  return output
end
 
#
# create the init.ora pfile
#
def create_init_ora_file
 
  if $oracle_version == "10.2.0"
    init_ora = <<txt
      control_files = (#{$ctl1},#{$ctl2},#{$ctl3})
      undo_management = AUTO
      undo_tablespace = undotbs01
      db_name = #{$sid}
      db_block_size = 8192
      sga_max_size = 1073741824   # 1GB
      sga_target = 1073741824     # 1GB
txt
 
  elsif $oracle_version == "11.1.0"
 
    init_ora = <<txt
      # log_archive_dest_1='LOCATION=/home/oracle/apps/oracle/oracle/product/11.1.0/db_1/dbs/arch'
      # log_archive_format=%t_%s_%r.dbf
      db_block_size=8192
      open_cursors=300
      db_domain=""
      db_name=#{$sid}
      control_files = (#{$ctl1},#{$ctl2},#{$ctl3})
      compatible=11.1.0.0.0
      diagnostic_dest=#{$oracle_base}
      # memory_target=4294967296 # 4GB
      memory_target=1073741824 # 1GB
      processes=200
      sessions=225
      audit_file_dest=#{$oracle_base}/admin/#{$sid}/adump
      audit_trail=db
      remote_login_passwordfile=EXCLUSIVE
      dispatchers="(PROTOCOL=TCP) (SERVICE=#{$sid}XDB)"
      undo_tablespace=undotbs01
txt
 
  end
 
  puts init_ora
  f = "#{$oracle_home}/dbs/init#{$sid}.ora"
  File.new(f, "w")
  open(f, 'w') { |f| f.puts init_ora }
end
 
def create_password_file
  cmd="orapwd file=#{$oracle_home}/dbs/orapw#{$sid} password=#{$password}"
  puts cmd
  %x[#{cmd}]
end
 
def create_db_dirs
  if $oracle_version == "10.2.0"
    # datafiles dir
    %x[mkdir -p #{$oracle_home}/oradata/#{$sid}]
    # archive files dir
    %x[mkdir -p #{$oracle_home}/dbs/arch/#{$sid}]
    # dump files dirs
    %x[mkdir -p #{$oracle_home}/admin/#{$sid}/adump]
    %x[mkdir -p #{$oracle_home}/admin/#{$sid}/bdump]
    %x[mkdir -p #{$oracle_home}/admin/#{$sid}/cdump]
    %x[mkdir -p #{$oracle_home}/admin/#{$sid}/udump]
  elsif $oracle_version == "11.1.0"
    # note: oracle 11 stores database related files off of oracle_base, not off of oracle_home.
    # adump directory
    %x[mkdir -p #{$oracle_base}/admin/#{$sid}/adump]
    %x[mkdir -p #{$oracle_base}/admin/#{$sid}/dpdump]
    # pfile location
    %x[mkdir -p #{$oracle_base}/admin/#{$sid}/pfile]
    # oradata
    %x[mkdir -p #{$oracle_base}/oradata/#{$sid}]
    # dbs
    %x[mkdir -p #{$oracle_home}/dbs]
    # dbs arch
    %x[mkdir -p #{$oracle_home}/dbs/arch]
  end
end
 
def prompt(s)
  puts "#{s} Continue? [Y/N]"
  choice = STDIN.gets
  choice.chomp!
  exit if choice != "Y"
end
 
################################################################################################
 
##############################################################################
$sid=ARGV[0]
if $sid == nil 
  puts "SID must be passed as a parameter. Exiting."
  exit
end
 
sidconfig = "#{$sid}.conf"
"Loading sidconfig for #{$sid}..."
load sidconfig
 
$debug=false
##############################################################################
 
prompt "About to create database for SID: #{$sid}"
 
prompt "Create dbdirs"
create_db_dirs
 
prompt "Create init.ora"
create_init_ora_file
 
prompt "Create password file"
create_password_file
 
prompt "Create spfile"
sqlplus "create spfile from pfile;"
 
prompt "Startup nomount"
sqlplus "startup nomount"
 
prompt "Create database"
sql = <<txt
CREATE DATABASE "#{$sid}"
   USER SYS IDENTIFIED BY #{$password}
   USER SYSTEM IDENTIFIED BY #{$password}
   LOGFILE GROUP 1 ('#{$redo1}') SIZE 100M,
           GROUP 2 ('#{$redo2}') SIZE 100M,
           GROUP 3 ('#{$redo3}') SIZE 100M
   MAXLOGFILES 16
   MAXLOGMEMBERS 5
   MAXLOGHISTORY 1
   MAXDATAFILES 200
   MAXINSTANCES 8
   CHARACTER SET UTF8
   NATIONAL CHARACTER SET AL16UTF16
     DATAFILE '#{$data_home}/system01.dbf' SIZE 325M REUSE
   EXTENT MANAGEMENT LOCAL
   SYSAUX 
     DATAFILE '#{$data_home}/sysaux01.dbf' SIZE 325M REUSE
   DEFAULT TEMPORARY TABLESPACE temp
     TEMPFILE '#{$data_home}/temp01.dbf' SIZE 100M REUSE
   UNDO TABLESPACE undotbs01 
     DATAFILE '#{$data_home}/undotbs01.dbf' SIZE 200M REUSE AUTOEXTEND ON MAXSIZE UNLIMITED;
txt
 
#CHARACTER SET WE8ISO8859P1
#NATIONAL CHARACTER SET UTF8
 
sqlplus sql
 
 
sql = <<txt
 
CREATE TABLESPACE users LOGGING
     DATAFILE '#{$data_home}/users01.dbf' 
     SIZE 100M REUSE AUTOEXTEND ON NEXT 1280K MAXSIZE UNLIMITED 
     EXTENT MANAGEMENT LOCAL;
txt
 
prompt "Create users tbs"
sqlplus sql
 
prompt "Run catalog.sql"
sqlplus "@?/rdbms/admin/catalog.sql"
 
prompt "Run catproc.sql"
sqlplus "@?/rdbms/admin/catproc.sql"
Posted in Uncategorized | Tagged , , | Leave a comment

Very Basic REST and the Google Provisioning API In Ruby

In this post, I’m walking the user through an app that talks to Google via their Provisioning API.

I’ll be talking about the following:

- Ruby/Rails
- HTTP Basic Authorization
- REST
- Google GData and Google Provisioning API

This article is a work in progress.

provisioner_google.rb

 
 def retrieve_user(username)
      raise ArgumentError, "username blank", caller if username.empty?
      resp = @rest_client.GET("/a/feeds/foo.com/user/2.0/#{username}")
      feed = Document.new(resp.body)
      unless ! XPath.match( feed, "/AppsForYourDomainErrors" ).empty?
        xn = ProvXn.new
        xn.givenname = feed.elements["//apps:name"].attributes["givenName"]
        xn.familyname = feed.elements["//apps:name"].attributes["familyName"]
        xn.username = feed.elements["//apps:login"].attributes["userName"]
        return xn
      end
      error_code = feed.elements["//AppsForYourDomainErrors/error"].attributes["errorCode"]
      reason = feed.elements["//AppsForYourDomainErrors/error"].attributes["reason"]
      if error_code == '1301'
        raise ActiveRecord::RecordNotFound
      else
        raise ProvisionerException.new(error_code, reason), "unhandled error"
      end
    end
 
    # currently only supports changes to password.                                                                                                                                                                                           
    def update_user_attributes(attributes)
      raise ArgumentError, "username blank", caller if attributes['username'].empty?
      path = "/a/feeds/foo.com/user/2.0/#{attributes["username"]}"
      xml = <<TXT                     
 
      <?xml version="1.0" encoding="UTF-8"?>                                                                                                                                                                                                 
      <atom:entry xmlns:atom="http://www.w3.org/2005/Atom" xmlns:apps="http://schemas.google.com/apps/2006" xmlns:gd="http://schemas.google.com/g/2005">                                                                                     
        <atom:id>https://apps-apis.google.com/a/feeds/foo.com/user/2.0/#{ attributes['username'] }</atom:id>                                                                                                                                
        <apps:login userName="#{ attributes['username'] }" password="#{ attributes['password'] }" suspended="false" admin="false" changePasswordAtNextLogin="false" agreedToTerms="true"/>                                                   
      </atom:entry>                                                                                                                                                                                                                          
 
TXT
      xmldoc = Document.new(xml)
      resp = @rest_client.PUT(path, xmldoc.to_s)
    end

Test.

Posted in Uncategorized | Tagged , , , , , | Leave a comment

Bombproofing: “Bonding” Multiple NICs to One Network Interface

Concept: Two network cards backing one IP address. One card fails, you’re still up.

http://www.redhat.com/docs/manuals/enterprise/RHEL-5-manual/Deployment_Guide-en-US/s2-modules-bonding.html

linux channel bonding

linux channel bonding

Example will show bonding of 2 network cards to one IP address (macs and IPs changed to protect the innocent).

Step 1: Create /etc/modprobe.conf entries

alias bond0 bonding

Step 2: Create ifcfg-bond0 file in /etc/sysconfig/network-scripts

DEVICE=bond0
BOOTPROTO=static
BROADCAST=999.999.999.255
IPADDR=999.999.999.999
NETMASK=255.255.255.0
NETWORK=999.999.999.0
ONBOOT=yes

Step 3: Modify existing ifcfg-eth0 and ifcfg-eth1 files in /etc/sysconfig/network-scripts

(ifcfg-eth0):

DEVICE=eth0
BOOTPROTO=none
HWADDR=00:xx:xx:xx:xx:xx
ONBOOT=yes
MASTER=bond0
SLAVE=yes
USERCTL=0

(ifcfg-eth1):

DEVICE=eth1
BOOTPROTO=none
HWADDR=xx:x3:xx:xx:88:xx
ONBOOT=yes
MASTER=bond0
SLAVE=yes
USERCTL=no

Step 4: Re-initialize network

/sbin/service network restart
Posted in Uncategorized | Tagged , , | Leave a comment

Chiclet

Posted in Uncategorized | Tagged | Leave a comment