class OpenShift::ApacheDB

Present an API to Apache's DB files for mod_rewrite.

This is a bit complicated since there don't appear to be a DB file format common to ruby and Apache that does not have corruption issues. The only format they can agree on is text, which is slow for 10's of thousands of entries. Unfortunately, that means we have to go through a convoluted process to populate the final Apache DB.

This locks down to one thread for safety. You MUST ensure that close is called to release all locks. Close also syncs changes to Apache if data was modified.

Constants

MAPNAME
NEWDB
READER
SUFFIX
WRCREAT
WRITER

Public Class Methods

new(flags=nil) click to toggle source
# File lib/openshift-origin-node/model/frontend_httpd.rb, line 859
def initialize(flags=nil)
  @closed = false

  if self.MAPNAME.nil?
    raise NotImplementedError.new("Must subclass with proper map name.")
  end

  @config = OpenShift::Config.new
  @basedir = @config.get("OPENSHIFT_HTTP_CONF_DIR")

  @mode = 0640

  if flags.nil?
    @flags = READER
  else
    @flags = flags
  end

  @filename = File.join(@basedir, self.MAPNAME)

  @lockfile = @@LOCKFILEBASE + '.' + self.MAPNAME + self.SUFFIX + '.lock'

  super()

  # Each filename needs its own mutex and lockfile
  @@LOCKS[@lockfile].lock

  begin
    @lfd = File.new(@lockfile, Fcntl::O_RDWR | Fcntl::O_CREAT, 0640)

    if writable?
      @lfd.flock(File::LOCK_EX)
    else
      @lfd.flock(File::LOCK_SH)
    end

    if @flags != NEWDB
      reload
    end

  rescue
    begin
      if not @lfd.nil?
        @lfd.close()
      end
    ensure
      @@LOCKS[@lockfile].unlock
    end
    raise
  end

end
open(flags=nil) { |inst| ... } click to toggle source

Preferred method of access is to feed a block to open so we can guarantee the close.

# File lib/openshift-origin-node/model/frontend_httpd.rb, line 1015
def self.open(flags=nil)
  inst = new(flags)
  if block_given?
    begin
      return yield(inst)
    rescue
      @flags = nil # Disable flush
      raise
    ensure
      if not inst.closed?
        inst.close
      end
    end
  end
  inst
end

Public Instance Methods

callout() click to toggle source
# File lib/openshift-origin-node/model/frontend_httpd.rb, line 943
def callout
  # Use Berkeley DB so that there's no race condition between
  # multiple file moves.  The Berkeley DB implementation creates a
  # scratch working file under certain circumstances.  Use a
  # scratch dir to protect it.
  Dir.mktmpdir([File.basename(@filename) + ".db-", ""], File.dirname(@filename)) do |wd|
    tmpdb = File.join(wd, 'new.db')

    httxt2dbm = ["/usr/bin","/usr/sbin","/bin","/sbin"].map {|d| File.join(d, "httxt2dbm")}.select {|p| File.exists?(p)}.pop
    if httxt2dbm.nil?
      logger.warn("WARNING: no httxt2dbm command found, relying on PATH")
      httxt2dbm="httxt2dbm"
    end

    cmd = %Q{#{httxt2dbm} -f DB -i #{@filename}#{self.SUFFIX} -o #{tmpdb}}
    out,err,rc = Utils::oo_spawn(cmd)
    if rc == 0
      logger.debug("httxt2dbm: #{@filename}: #{rc}: stdout: #{out} stderr:#{err}")
      begin
        oldstat = File.stat(@filename + '.db')
        File.chown(oldstat.uid, oldstat.gid, tmpdb)
        File.chmod(oldstat.mode & 0777, tmpdb)
      rescue Errno::ENOENT
      end
      FileUtils.mv(tmpdb, @filename + '.db', :force=>true)
    else
      logger.error("ERROR: failure httxt2dbm #{@filename}: #{rc}: stdout: #{out} stderr:#{err}") unless rc == 0
    end
  end
end
close() click to toggle source
# File lib/openshift-origin-node/model/frontend_httpd.rb, line 996
def close
  @closed=true
  begin
    begin
      self.flush
    ensure
      @lfd.close() unless @lfd.closed?
    end
  ensure
    @@LOCKS[@lockfile].unlock if @@LOCKS[@lockfile].locked?
  end
end
closed?() click to toggle source
# File lib/openshift-origin-node/model/frontend_httpd.rb, line 1009
def closed?
  @closed
end
decode_contents(f) click to toggle source
# File lib/openshift-origin-node/model/frontend_httpd.rb, line 912
def decode_contents(f)
  f.each do |l|
    path, dest = l.strip.split
    if (not path.nil?) and (not dest.nil?)
      self.store(path, dest)
    end
  end
end
encode_contents(f) click to toggle source
# File lib/openshift-origin-node/model/frontend_httpd.rb, line 921
def encode_contents(f)
  self.each do |k, v|
    f.write([k, v].join(' ') + "\n")
  end
end
flush() click to toggle source
# File lib/openshift-origin-node/model/frontend_httpd.rb, line 974
def flush
  if writable?
    File.open(@filename + self.SUFFIX + '-', Fcntl::O_RDWR | Fcntl::O_CREAT | Fcntl::O_TRUNC, 0640) do |f|
      encode_contents(f)
    end

    # Ruby 1.9 Hash preserves order, compare files to see if anything changed
    if FileUtils.compare_file(@filename + self.SUFFIX + '-', @filename + self.SUFFIX)
      FileUtils.rm(@filename + self.SUFFIX + '-', :force=>true)
    else
      begin
        oldstat = File.stat(@filename + self.SUFFIX)
        FileUtils.chown(oldstat.uid, oldstat.gid, @filename + self.SUFFIX + '-')
        FileUtils.chmod(oldstat.mode & 0777, @filename + self.SUFFIX + '-')
      rescue Errno::ENOENT
      end
      FileUtils.mv(@filename + self.SUFFIX + '-', @filename + self.SUFFIX, :force=>true)
      callout
    end
  end
end
reload() click to toggle source
# File lib/openshift-origin-node/model/frontend_httpd.rb, line 927
def reload
  begin
    File.open(@filename + self.SUFFIX, Fcntl::O_RDONLY) do |f|
      decode_contents(f)
    end
  rescue Errno::ENOENT
    if not [WRCREAT, NEWDB].include?(@flags)
      raise
    end
  end
end
update_block() { |deletions, updates, k, v| ... } click to toggle source

Public, update using a block The block is called for each key, value pair of the hash and uses the following parameters:

deletions   Array of keys to delete
updates     Hash of key->value pairs to add/update
k, v        Key and value of this iteration
# File lib/openshift-origin-node/model/frontend_httpd.rb, line 1038
def update_block
  deletions = []
  updates = {}
  self.each do |k, v|
    yield(deletions, updates, k, v)
  end
  self.delete_if { |k, v| deletions.include?(k) }
  self.update(updates)
end
writable?() click to toggle source
# File lib/openshift-origin-node/model/frontend_httpd.rb, line 939
def writable?
  [WRITER, WRCREAT, NEWDB].include?(@flags)
end