#!/usr/bin/python2.7
# -*- coding: utf-8 -*-
#
# Univention Nagios
#
# Copyright 2004-2021 Univention GmbH
#
# https://www.univention.de/
#
# All rights reserved.
#
# The source code of this program is made available
# under the terms of the GNU Affero General Public License version 3
# (GNU AGPL V3) as published by the Free Software Foundation.
#
# Binary versions of this program provided by Univention to you as
# well as other copyrighted, protected or trademarked materials like
# Logos, graphics, fonts, specific documentations and configurations,
# cryptographic keys etc. are subject to a license agreement between
# you and Univention and not subject to the GNU AGPL V3.
#
# In the case you use this program under the terms of the GNU AGPL V3,
# the program is provided in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public
# License with the Debian GNU/Linux or Univention distribution in file
# /usr/share/common-licenses/AGPL-3; if not, see
# <https://www.gnu.org/licenses/>.

import commands
import getopt
import sys
import pickle
import os


class ReplicationCheck:

	def __init__(self):
		self.PROGNAME = 'check_univention_replication'
		self.REVISION = '1.0'
		self.FAILED_LDIF_FN = '/var/lib/univention-directory-replication/failed.ldif'
		self.CACHEFN = '/var/lib/univention-nagios/check_univention_replication.cache'
		self.PROG_GETNOTIFIERID = '/usr/share/univention-directory-listener/get_notifier_id.py'
		self.PROG_GETLISTENERID = 'cat /var/lib/univention-directory-listener/notifier_id'
		self.diff_warning = 0
		self.diff_critical = 0
		self.history_size = 0
		self.verbose = 0

		self.STATE = {
			'OK': 0,
			'WARNING': 1,
			'CRITICAL': 2,
			'UNKNOWN': 3
		}
		self.history = []

	def print_revision(self):
		print '%s: version %s' % (self.PROGNAME, self.REVISION)

	def print_usage(self):
		print 'Usage: %s [-v] [-w <cnt>] [-c <cnt>] [-n <cnt>]' % self.PROGNAME
		print 'Usage: %s --help' % self.PROGNAME
		print 'Usage: %s --version' % self.PROGNAME

	def print_help(self):
		self.print_revision()
		print ''
		self.print_usage()
		print ''
		print ' -v        verbose debug output'
		print ' -w <cnt>  WARNING if difference of transaction IDs is >= <cnt>'
		print ' -c <cnt>  CRITICAL if difference of transaction IDs is >= <cnt>'
		print ' -n <cnt>  CRITICAL if no change of transaction ID over last <cnt> checks'

	def load_history(self):
		if os.path.exists(self.CACHEFN) and os.stat(self.CACHEFN)[6] > 0:
			try:
				f = open(self.CACHEFN, 'r')
				cnt = pickle.load(f)
				for i in range(cnt):
					nid = pickle.load(f)
					lid = pickle.load(f)
					self.history.append((nid, lid))
				f.close()

				if self.verbose > 1:
					print 'History loaded:', self.history
			except:
				if self.verbose > 1:
					print 'Loading history failed!'
		else:
			if self.verbose > 1:
				print 'History not loaded!'

	def save_history(self):
		f = open(self.CACHEFN, 'w')
		pickle.dump(len(self.history), f)
		for i in range(len(self.history)):
			pickle.dump(self.history[i][0], f)
			pickle.dump(self.history[i][1], f)

		if self.verbose > 1:
			print 'History saved:', self.history

	def exit_with_status(self, state, msg, nid, lid):
		print '%s: %s (nid=%s lid=%s)' % (state, msg, nid, lid)
		sys.exit(self.STATE[state])

	def main(self):
		# parse command line
		try:
			(opts, pargs) = getopt.getopt(sys.argv[1:], 'c:hn:vw:', ['help', 'version'])
		except:
			self.print_usage()
			sys.exit(self.STATE['UNKNOWN'])

		# get command line data
		for opt in opts:
			if opt[0] == '-c':
				self.diff_critical = int(opt[1])
				if self.diff_critical < 0:
					self.diff_critical = 0
			elif opt[0] == '-h' or opt[0] == '--help':
				self.print_help()
				sys.exit(self.STATE['UNKNOWN'])
			elif opt[0] == '-n':
				self.history_size = int(opt[1])
				if self.history_size < 1:
					self.history_size = 1
			elif opt[0] == '-v':
				self.verbose += 1
			elif opt[0] == '-w':
				self.diff_warning = int(opt[1])
				if self.diff_warning < 0:
					self.diff_warning = 0
			elif opt[0] == '--version':
				self.print_revision()
				sys.exit(self.STATE['UNKNOWN'])

		self.load_history()

		# get actual transaction id
		notifier_id = commands.getoutput(self.PROG_GETNOTIFIERID)
		listener_id = commands.getoutput(self.PROG_GETLISTENERID)

		# DEBUG CODE
		if len(pargs) == 2:
			notifier_id = int(pargs[0])
			listener_id = int(pargs[1])

		# add values to history
		self.history.insert(0, (notifier_id, listener_id))
		if len(self.history) > self.history_size:
			del self.history[self.history_size:]

		self.save_history()

		# CRITICAL if failed.ldif exists
		if os.path.exists(self.FAILED_LDIF_FN):
			self.exit_with_status('CRITICAL', 'failed.ldif exists', notifier_id, listener_id)

		if notifier_id == listener_id:
			self.exit_with_status('OK', 'replication complete', notifier_id, listener_id)
		else:
			# check if listener id changed during last checks
			change = 0
			beenequal = 0
			for (nid, lid) in self.history:
				if nid == lid:
					beenequal = 1
				if listener_id != lid:
					change = 1
			if change == 0 and beenequal == 0 and len(self.history) == self.history_size:
				self.exit_with_status('CRITICAL',
					'no change of listener transaction id for last %s checks' % len(self.history),
					notifier_id, listener_id)

			# check if difference of nid and lid has been larger than given values
			mindiff = None
			for (nid, lid) in self.history:
				diff = int(nid) - int(lid)
				if not mindiff:
					mindiff = diff
				else:
					if mindiff > diff:
						mindiff = diff

			if self.verbose:
				print 'minimal difference:', mindiff

			if self.diff_critical and self.diff_critical <= mindiff:
				self.exit_with_status('CRITICAL',
					'difference was >= %s over last %s checks' % (self.diff_critical, len(self.history)),
					notifier_id, listener_id)
			if self.diff_warning and self.diff_warning <= mindiff:
				self.exit_with_status('WARNING',
					'difference was >= %s over last %s checks' % (self.diff_warning, len(self.history)),
					notifier_id, listener_id)

		self.exit_with_status('OK', 'replication is in process', notifier_id, listener_id)


obj = ReplicationCheck()
obj.main()
