class ICFS::Utils::Backup

Backup and restore utilities

Public Class Methods

new(cache, store, log) click to toggle source

Instance

@param cache [Cache] The live cache @param store [Store] The live store @param log [Logger] Where to log

# File lib/icfs/utils/backup.rb, line 33
def initialize(cache, store, log)
  @cache = cache
  @store = store
  @log = log
end

Public Instance Methods

copy(cid, dest, lnum_min=1, lnum_max=0) click to toggle source

Transfer a case to another store

@param cid [String] The case ID @param dest [Store] The destination store @param lnum_max [Integer] The highest log @param lnum_min [Integer] The lowest log

# File lib/icfs/utils/backup.rb, line 82
def copy(cid, dest, lnum_min=1, lnum_max=0)
  @log.info('ICFS copy: %s %d-%d' % [cid, lnum_min, lnum_max])

  # if no max specified, pull from current
  if lnum_max == 0
    json = @cache.current_read(cid)
    cur = Items.parse(json, 'current', Items::ItemCurrent)
    lnum_max = cur['log']
  end

  if lnum_min > lnum_max
    raise ArgumentError, 'ICFS copy, log num min is larger than max'
  end

  # each log
  lnum = lnum_min
  while lnum <= lnum_max

    # copy the log
    log = _copy_item(dest,
      'log %d' % lnum,
      :log_read,
      :log_write,
      [cid, lnum],
      Items::ItemLog
    )
    if !log
      lnum += 1
      next
    end

    # entry
    if log['entry']
      enum = log['entry']['num']
      _copy_item(dest,
        'entry %d-%d' % [enum, lnum],
        :entry_read,
        :entry_write,
        [cid, enum, lnum]
      )
    end

    # index
    if log['index']
      xnum = log['index']['num']
      _copy_item(dest,
        'index %d-%d' % [xnum, lnum],
        :index_read,
        :index_write,
        [cid, xnum, lnum]
      )
    end

    # action
    if log['action']
      anum = log['action']['num']
      _copy_item(dest,
        'action %d-%d' % [anum, lnum],
        :action_read,
        :action_write,
        [cid, anum, lnum]
      )
    end

    # case
    if log['case']
      _copy_item(dest,
        'case %d' % lnum,
        :case_read,
        :case_write,
        [cid, lnum]
      )
    end

    # files
    if log['files_hash']
      log['files_hash'].each_index do |fraw|
        fnum = fraw + 1

        @log.debug('ICFS copy: file %d-%d-%d' % [enum, lnum, fnum])

        # read
        fi = @store.file_read(cid, enum, lnum, fnum)
        if !fi
          @log.warn('ICFS copy: file %d-%d-%d missing' %
              [enum, lnum, fnum])
          next
        end

        # copy
        tmp = dest.tempfile
        IO.copy_stream(fi, tmp)
        @store.close(fi)

        # write
        dest.file_write(cid, enum, lnum, fnum, tmp)
      end
    end

    lnum += 1
  end

end
restore(cid, src, lnum_min=0, lnum_max=0) click to toggle source

Restore a backup into a case

@param cid [String] The case ID @param src [Store] Source store @param lnum_max [Integer] The highest log @param lnum_min [Integer] The lowest log

# File lib/icfs/utils/backup.rb, line 227
def restore(cid, src, lnum_min=0, lnum_max=0)
  @log.info('ICFS restore: %s %d-%d' % [cid, lnum_min, lnum_max])

  # take lock
  @cache.lock_take(cid)
  begin

    # read current
    json = @cache.current_read(cid)
    if json
      cur = Items.parse(json, 'current', Items::ItemCurrent)
    else
      cur = {
        'icfs' => 1,
        'caseid' => cid,
        'log' => 1,
        'entry' => 1,
        'action' => 0,
        'index' => 0
      }
    end

    # if no min specified, pull from current or default to 1
    lnum_min = cur['log'] if lnum_min == 0

    # sanity check min & max
    if (lnum_min > lnum_max) && (lnum_max != 0)
      raise ArgumentError: 'ICFS restore, log min is larger than max'
    end

    # max entry, action, index
    emax = cur['entry']
    amax = cur['action']
    imax = cur['index']

    # each log
    lnum = lnum_min
    llast = nil
    while lnum != lnum_max

      # copy the log
      log, litem = _restore_item(src,
        'log %d' % lnum,
        :log_read,
        :log_write,
        [cid, lnum],
        [cid, lnum],
        Items::ItemLog
      )

      # no log - all done
      if !log
        break
      else
        llast = litem
      end

      # entry
      if log['entry']
        enum = log['entry']['num']
        _restore_item(src,
          'entry %d-%d' % [enum, lnum],
          :entry_read,
          :entry_write,
          [cid, enum, lnum],
          [cid, enum]
        )
        emax = enum if enum > emax
      end

      # index
      if log['index']
        xnum = log['index']['num']
        _restore_item(src,
          'index %d-%d' % [xnum, lnum],
          :index_read,
          :index_write,
          [cid, xnum, lnum],
          [cid, xnum]
        )
        imax = xnum if xnum > imax
      end

      # action
      if log['action']
        anum = log['action']['num']
        _restore_item(src,
          'action %d-%d' % [anum, lnum],
          :action_read,
          :action_write,
          [cid, anum, lnum],
          [cid, anum]
        )
        amax = anum if anum > amax
      end

      # case
      if log['case']
        _restore_item(src,
          'case %d' % lnum,
          :case_read,
          :case_write,
          [cid, lnum],
          [cid]
        )
      end

      # files
      if log['files_hash']
        log['files_hash'].each_index do |fraw|
          fnum = fraw + 1

          @log.debug('ICFS restore: file %d-%d-%d' % [enum, lnum, fnum])

          # read
          fi = src.file_read(cid, enum, lnum, fnum)
          if !fi
            @log.warn('ICFS restore: file %d-%d-%d missing' %
              [enum, lnum, fnum])
            next
          end

          # copy
          tmp = @store.tempfile
          IO.copy_stream(fi, tmp)
          src.close(fi)

          # write
          @store.file_write(cid, enum, lnum, fnum, tmp)
        end
      end

      lnum += 1
    end

    # write current
    cur = {
      'icfs' => 1,
      'caseid' => cid,
      'log' => lnum-1,
      'entry' => emax,
      'action' => amax,
      'index' => imax,
      'hash' => ICFS.hash(llast)
    }
    nitem = Items.generate(cur, 'current', Items::ItemCurrent)
    @cache.current_write(cid, nitem)

  ensure
    # release lock
    @cache.lock_release(cid)
  end

end

Private Instance Methods

_copy_item(dest, title, read, write, args, val=nil) click to toggle source

Copy an item

# File lib/icfs/utils/backup.rb, line 43
def _copy_item(dest, title, read, write, args, val=nil)
  @log.debug('ICFS copy: %s' % title)

  # read the item
  json = @store.send(read, *args)
  if !json
    @log.warn('ICFS copy: %s is missing' % title)
    return nil
  end

  # parse the item if requested
  if val
    obj = JSON.parse(json)
    err = Validate.check(obj, val)
    if err
      @log.error('ICFS copy: %s bad format' % title)
      return nil
    end
  end

  # write the item
  dest.send(write, *args, json)
  return obj

rescue JSON::ParserError
  @log.error('ICFS copy: %s bad JSON' % title)
  return nil
end
_restore_item(src, title, read, write, args_st, args_ca, val=nil) click to toggle source

Restore an item

# File lib/icfs/utils/backup.rb, line 190
def _restore_item(src, title, read, write, args_st, args_ca, val=nil)
  @log.debug('ICFS restore: %s' % title)

  # read the item
  json = src.send(read, *args_st)
  if !json
    @log.warn('ICFS restore: %s is missing' % title)
    return nil
  end

  # parse item if requested
  if val
    obj = JSON.parse(json)
    err = Validate.check(obj, val)
    if err
      @log.error('ICFS restore: %s bad format' % title)
      return nil
    end
  end

  # write the item to store & cache
  @store.send(write, *args_st, json)
  @cache.send(write, *args_ca, json)
  return [obj, json]

end