#!/usr/bin/perl
#
#  $Id$
# -----------------------------------------------------------------------------
#  SM_DBLOG - replicate a log/sm/dblog into sm_dblog DB table
# -----------------------------------------------------------------------------
#  Author: Alex Titov
#  QA by:
#  Copyright: videoNEXT LLC
# -----------------------------------------------------------------------------
#
# sm_dblog:
# 1. starts and managed by sm_engine
# 1. has own connection to MASTER database
# 2. periodically (once in 10 seconds) reads log/sm/dblog and post new records
# 3. once in hour clenup dblog and keep only 20 record per ID
# 4. in case of problem with DB few dblog records may be lost
#
# for REQ see: Rally S623:Storage Manager TA1112: database replication
#
use warnings;
use strict;
use SKM::DB;
use Data::Dumper;
use File::Basename qw(dirname);
use lib dirname(__FILE__).'/../lib';              # find  SM::Config here
use SM::Config ':all';
use Node::Conf;

# CONS ------------------------------------------------------------------------
my $SMDBLOG   =(split(/\//,$0))[-1];	        # the actual name of the prog
my $APL       =$ENV{APL};
my $INTERVAL  =5;                               # 5 (secounds) is recommended
my $DBLOG     =SM_DBLOG.'/dblog';               # name of log file
my $STAGE     =SM_DBLOG.'/dblog.stage';         # name staging-file
my $CHUNK_DROP_LOG="$APL/var/log/sm/stat/sm_writer_chunk_drop.log";
my $nodeid=UNI;                                 # char22 nodeid in domain

# VARS ------------------------------------------------------------------------
my $dbm;                                     # DB handler
my %dbs;

# SUBS ------------------------------------------------------------------------

sub db_master{
 eval { $dbm->disconnect() if $dbm; $dbm=''; }; # disconnect if defined
 for (my $i=1;$i<6;$i++) {
   eval {
     $dbm=DBMaster({PrintError=>1,'RaiseError' => 1});
     $dbm->{ShowErrorStatement} = 1;
     $dbs{INSERT}=$dbm->prepare('insert into audit(objid,category,parameters) values(?,?,?)');
     $dbs{LOG_INS}=$dbm->prepare(
             "insert into SM_DBLOG(NODEID,ID,MODULE,LINE,LEVEL,NOTE)\n"
             ."values(?,?,?,?,?,?)");
  };
   if($@) {
     SM_LOG->logdie("Attempt $i (final). Cannot connect to master: $@") if $i>=5;
     SM_LOG->error("Attempt $i. Cannot connect to master: $@");
     SM_LOG->error('Sleep '. $i*30 . ' before next attempt');
     sleep($i*30);
   } else {
     last;                                   # exit cycle if OK
   }
 }
 $dbm->{FetchHashKeyName} = 'NAME_uc';
 $dbm->{ShowErrorStatement}=1;
 SM_LOG->debug("Connected to master db");
}

#------------------------------------------------------------------------------
# 1. check dblog 
# 2. exit if empty
# 3. move dblog to dblog.stage
# 4. read and post record into DB
# 5. remove dblog.stage
#------------------------------------------------------------------------------

sub upload_dblog {
  return if ! -f $DBLOG;                           # file not exists
  return if   -z $DBLOG;                           # file is empty  
  unlink $STAGE if -f $STAGE;                      # remove stage copy if exits
  my $nconf=NodeConf();
  eval {
    rename($DBLOG, $STAGE) || die "FILE:Cannot move $DBLOG to stage $STAGE";
    open(STAGE,$STAGE)     || die "FILE:Cannot read $STAGE";
    $dbm->{AutoCommit} = 0;        # disable auto-commit (performance)
    while(<STAGE>) {
     #1215780978|sm_tool|255|INFO|7767a069-34ac-452d-87cb-ff306f584758|note SUCCESS
     #   $1        $2     $3  $4              $5                         $6
     next if not /^(\d+)\|(\w+)\|(\d+)\|(\w+)\|([\w|-]*)\|(.+)$/;
     my ($msg,$uuid,$prog,$line,$level)=($6,$5,$2,$3,$4);
     eval{
#old    $dbm->do("insert into SM_DBLOG(NODEID,ID,MODULE,LINE,LEVEL,NOTE)\n"
#old             ."values(?,?,?,?,?,?)\n", undef,($nodeid,$uuid,$prog,$3,$4,$msg));
        $dbs{LOG_INS}->execute($nodeid,$uuid,$prog,$line,$level,$msg);
        $msg=~s/"/'/g;   $msg=~s/\[/\(/g;  $msg=~s/\]/\)/g;
        if($prog eq 'sm_wipe') {
           $dbs{INSERT}->execute($nconf->{OBJID},38,qq([["$msg"]]));
        }else {
           $dbs{INSERT}->execute($nconf->{OBJID},30,qq([["$uuid","$msg"]]));
        }
     }; #ignore insert errors
    }
    $dbm->commit;
    $dbm->{AutoCommit} = 1;        # enable auto-commit
    close STAGE;
    unlink $STAGE;
  }; #end eval
  if($@) {
    SM_LOG->error($@),return  if $@=~/^FILE:/;
    SM_LOG->logdie($@);                           # cannot fully handle DB error
  }
}
#-------------------------------------------------------------------------------
# clean_dbloga initially keep records for last 12 hours only
#-------------------------------------------------------------------------------
sub clean_dblog {
  SM_LOG->info("clean dblog");
  $dbm->do("delete from SM_DBLOG where now() at time zone 'UTC' - stime > interval'12 hours'"); 
}
#------------------------------------------------------------------------------
# Update SM cache status in sm_cache table
# Read last dropped chunk timestamp from sm_writer_chunk_drop.log and write to sm_cache
# Get current cache total size and usage and write to sm_cache
#------------------------------------------------------------------------------
sub update_cache_status {
    #------------------------ Get current cache status/usage
    my ($status,$total,$free)=split /\s+/,`$APL/cache/bin/cachestatus`;
    if ($status ne 'on') {
	$total=$free=0;
    }
    my $usage=$total-$free;
    #------------------------ Get recent record in chunk_drop.log
    my $chunk_loss_ts;
    if (-f $CHUNK_DROP_LOG and ! -z $CHUNK_DROP_LOG and open FH, $CHUNK_DROP_LOG) {
	seek FH, -256, 2; # EOF - 256 chars
        local $/=undef;
        my $tail=<FH>;
        close FH;
        $chunk_loss_ts=$1 if (split /\n/,$tail) [-1] =~ /^(\d+)\s/;
    }
    # ----------------------- Update SM_CACHE
    eval {
	my $rows = $dbm->do(
	    "update SM_CACHE set SIZE=?,USAGE=? ,CHUNK_LOSS_TS=to_timestamp(?) where NODEID=?",
	    undef,$total,$usage,$chunk_loss_ts,$nodeid);
	if($rows<1) {        # record for NODEID is missing, Inserting...
    	    $dbm->do("insert into SM_CACHE (NODEID,SIZE,USAGE,CHUNK_LOSS_TS) ".
		"values (?,?,?,to_timestamp(?))",undef,$nodeid,$total,$usage,$chunk_loss_ts);
	}
    };
    if ($@) {
	SM_LOG->logdie($@);                           # cannot fully handle DB error
    }
}

# MAIN =========================================================================
 my $cycles_in_hour=int(3600/$INTERVAL);
 my $last_cache_upd=0;
 SM_WritePid($SMDBLOG);                             # write pid for a first time
 if (! $nodeid=~/^\w{22}$/) {
    sleep 15;                                       # sleep prevents fast restart
    SM_LOG->logdie("wrong UNI=$nodeid, $$SMDBLOG cannot operate");
 }
 SM_LOG->info("$SMDBLOG starts");
 db_master;                                         # connect to database
 for(my $cycle=1;;$cycle++) { # -------------------- this is main loop
   upload_dblog;
   update_cache_status if not $cycle%10;	    # update SM cache status every 50 sec
   clean_dblog      if not $cycle%$cycles_in_hour;  # run clean one in hour
   sleep $INTERVAL;
   SM_WritePid($SMDBLOG);
 }

END {
  $dbm->disconnect() if ($dbm);   #disconnect from databases
}

