#!/usr/bin/python3
# -*- coding: utf-8 -*-
#
# Univention Nagios
#
# Copyright 2004-2022 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 getopt
import sys
import os
import re


class I2O_RAIDCheck:

	def __init__(self):
		self.PROGNAME = 'check_univention_i2o_raid'
		self.REVISION = '1.0'
		self.INPUTFN = '/var/lib/univention-nagios/check_univention_i2o_raid.status'
		self.verbose = 0
		self.device = 0
		self.option = None
		self.content = {}
		self.msg = ''
		self.str_ok = ['Optimal']
		self.str_warning = ['Rebuilding', 'Reconstruct', 'Reconstructing', 'Replaced Drive', 'Expanding', 'Warning', 'Verify']
		self.str_critical = ['Degraded', 'Dead', 'Failed', 'Error', 'Missing']

		self.re_other = re.compile(r'^\s*(d(\d{1,2})b\d{1,2}t\d{1,2}d\d{1,2})\s+(RAID|Disk).*\W(\d+MB)\s+\W(.*?)(?: \d{1,3}%)?$')
		self.re_controller = re.compile(r'^(d(\d{1,2})) -- --\s+(\w+)\s+\w+\s+\w+\s+\w+\s+[\w\.\_\-]+\s+([\.\-\_\w]+)\s*(%s|%s|%s)\s*$' % (
			'|'.join(self.str_ok), '|'.join(self.str_warning), '|'.join(self.str_critical))
		)

		self.state = {
			'OK': 0,
			'WARNING': 1,
			'CRITICAL': 2,
			'UNKNOWN': 3
		}

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

	def print_usage(self):
		print('Usage: %s [-v [-v]] [-d <num>] (-l|-p|-c|-r)' % 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(' -vv       intense debug output')
		print(' -d <num>  select device (default: 0)')
		print(' -p        test status of physical device')
		print(' -l        test status of logical device')
		print(' -c        test status of controller device')
		print(' -r        test status of raid device')

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

	def load_inputfile(self):
		if not os.path.exists(self.INPUTFN):
			self.exit_with_status('UNKNOWN', 'file %s does not exist' % self.INPUTFN)

		try:
			with open(self.INPUTFN) as fd:
				self.content['all'] = fd.read()
		except EOFError as err:
			self.exit_with_status('UNKNOWN', 'error while reading %s, EOF error: %s' % (self.INPUTFN, err))
		except OSError as err:
			self.exit_with_status('UNKNOWN', 'error while reading %s, OS error: %s' % (self.INPUTFN, err))

		(self.content['physical'], self.content['logical'], self.content['controller'], self.content['raid']) = self.content['all'].split('\n\n')[0:4]

	def parse_data(self):
		returnstate = 'UNKNOWN'
		txt = self.content[self.option]

		for line in txt.splitlines():
			if self.verbose > 1:
				print(line)

			line = line.strip()

			addr = type = size = status = ''

			if self.option in ['physical', 'logical', 'raid']:
				result = self.re_other.match(line)
				if result:
					(addr, device, type, size, status) = result.groups()

					if int(device) == int(self.device):
						state = 'UNKNOWN'
						if status in self.str_critical:
							state = 'CRITICAL'
						elif status in self.str_warning:
							state = 'WARNING'
						elif status in self.str_ok:
							state = 'OK'

						if state == 'CRITICAL':
							returnstate = state
						elif state == 'WARNING' and returnstate not in ['CRITICAL']:
							returnstate = state
						elif state == 'OK' and returnstate not in ['CRITICAL', 'WARNING']:
							returnstate = state
						elif state == 'UNKNOWN' and returnstate not in ['CRITICAL']:
							# return WARNING if device is in unknown state
							returnstate = 'WARNING'

						if self.verbose == 0:
							self.msg += '%s %s, ' % (type, state)
						else:
							self.msg += '%s: %s with %s is %s, ' % (type, addr, size, state)
			else:
				result = self.re_controller.match(line)
				if result:
					(addr, device, type, serial, status) = result.groups()

					if int(device) == int(self.device):
						state = 'UNKNOWN'
						if status in self.str_critical:
							state = 'CRITICAL'
						elif status in self.str_warning:
							state = 'WARNING'
						elif status in self.str_ok:
							state = 'OK'

						if state == 'CRITICAL':
							returnstate = state
						elif state == 'WARNING' and returnstate not in ['CRITICAL']:
							returnstate = state
						elif state == 'OK' and returnstate not in ['CRITICAL', 'WARNING']:
							returnstate = state
						elif state == 'UNKNOWN' and returnstate not in ['CRITICAL']:
							# return WARNING if device is in unknown state
							returnstate = 'WARNING'

						if self.verbose == 0:
							self.msg += 'Controller %s, ' % state
						else:
							self.msg += 'Controller %s: %s with serial %s is %s, ' % (type, addr, serial, state)

		self.returnstate = returnstate
		self.msg = self.msg.rstrip(', ')

	def main(self):
		# parse command line
		try:
			(opts, pargs) = getopt.getopt(sys.argv[1:], 'cd:lprv', ['help', 'version'])
		except getopt.GetoptError:
			self.print_usage()
			sys.exit(self.state['UNKNOWN'])

		# get command line data
		for opt in opts:
			if opt[0] == '-h' or opt[0] == '--help':
				self.print_help()
				sys.exit(self.state['UNKNOWN'])
			elif opt[0] == '-v':
				self.verbose += 1
			elif opt[0] == '--version':
				self.print_revision()
				sys.exit(self.state['UNKNOWN'])
			elif opt[0] == '-d':
				self.device = opt[1]
			elif opt[0] == '-p':
				self.option = 'physical'
			elif opt[0] == '-l':
				self.option = 'logical'
			elif opt[0] == '-c':
				self.option = 'controller'
			elif opt[0] == '-r':
				self.option = 'raid'

		if not self.option:
			self.exit_with_status('UNKNOWN', 'choose one argument: -l, -p, -r or -c')

		# load input file
		self.load_inputfile()
		# parse input file and set return value and msg
		self.parse_data()
		# exit gracefully
		self.exit_with_status(self.returnstate, self.msg)


obj = I2O_RAIDCheck()
obj.main()
