package Huawei::Healthchecker;

use 5.006;
use strict;
use warnings;

=head1 NAME

Huawei::Healthchecker - The great new Huawei::Healthchecker!

=head1 VERSION

Version 0.01

=cut

our $VERSION = '0.01';


=head1 SYNOPSIS

Quick summary of what the module does.

Perhaps a little code snippet.

    use Huawei::Healthchecker;

    my $foo = Huawei::Healthchecker->new();
    ...

=head1 EXPORT

A list of functions that can be exported.  You can delete this section
if you don't export anything, such as for a purely object-oriented module.

=head1 SUBROUTINES/METHODS

=head2 function1

=cut

sub function1 {
}

=head2 function2

=cut

sub function2 {
}

=head1 AUTHOR

WENWU YAN, C<< <careline at 126.com> >>

=head1 BUGS

Please report any bugs or feature requests to C<bug-huawei-healthchecker at rt.cpan.org>, or through
the web interface at L<https://rt.cpan.org/NoAuth/ReportBug.html?Queue=Huawei-Healthchecker>.  I will be notified, and then you'll
automatically be notified of progress on your bug as I make changes.




=head1 SUPPORT

You can find documentation for this module with the perldoc command.

    perldoc Huawei::Healthchecker


You can also look for information at:

=over 4

=item * RT: CPAN's request tracker (report bugs here)

L<https://rt.cpan.org/NoAuth/Bugs.html?Dist=Huawei-Healthchecker>

=item * AnnoCPAN: Annotated CPAN documentation

L<http://annocpan.org/dist/Huawei-Healthchecker>

=item * CPAN Ratings

L<https://cpanratings.perl.org/d/Huawei-Healthchecker>

=item * Search CPAN

L<https://metacpan.org/release/Huawei-Healthchecker>

=back


=head1 ACKNOWLEDGEMENTS


=head1 LICENSE AND COPYRIGHT

This software is Copyright (c) 2019 by WENWU YAN.

This is free software, licensed under:

  The Artistic License 2.0 (GPL Compatible)


=cut


#!/usr/bin/env perl

use 5.012;
use warnings;
use Data::Dumper;

use Cwd qw 'cwd abs_path';
use POSIX qw 'strftime';
use Filse::Slurp;
use File::Basename;
use Config::Tiny;
use Net::Compare;


#设置时间戳
my $time = strftime( "%Y%m%d", localtime() );

#获取当前脚本绝对路径
my $dir = dirname( abs_path($0) );

#巡检结果-标量
my $result;

#初始化配置文件
usage () unless -f $dir . $split . "config.ini";
my $config = new Config::Tiny->read("config.ini");

#命令输出注释标记
my $comment = $dir . $split . $config->{"_"}{"comment"};
$comment =~ s/(\'|\")//;

#基线规则文件
my $rules = $config->{"rules"};
my @attr  = keys %{$rules};
my @files = grep {/\.cfg$|\.conf$|\.config$/} read_dir($config_dir);

#-------------------------------------------------------------------------
# 网络设备-硬件运行状态巡检
#-------------------------------------------------------------------------
my $device_module = catch_cmd("dis device", $config);
my $device_power = catch_cmd("dis device power", $config);
my $device_fan = catch_cmd("dis device fan", $config);
my $device_alarm = catch_cmd("dis device alarm hardware", $config);
my $device_temperature = catch_cmd("dis device temperature all", $config);
my $device_storage = catch_cmd("dis health", $config);

#-------------------------------------------------------------------------
# 网络设备-关键运行指标巡检
#-------------------------------------------------------------------------
my $device_cpu = catch_cmd("dis cpu", $config);
my $device_memory = catch_cmd("dis memory", $config);
my $device_ntp = catch_cmd("dis ntp status", $config);
my $device_vrrp = catch_cmd("dis vrrp", $config);
my $device_eth_trunk = catch_cmd("dis eth-trunk", $config);
my $device_err_down = catch_cmd("dis error-down recovery", $config);

#-------------------------------------------------------------------------
# 网络设备-硬件运行状态巡检 -- 设备模块检查
#-------------------------------------------------------------------------
sub module_status {
	#接收巡检命令行输出
	my $rev = shift;
	#如果没匹配到巡检输出直接返回
	return unless $rev;

	#模块巡检标量
	my $result;
	#初始化巡检结果告警项
	$result->{"alarm"}{"info"} = undef;
	$result->{"alarm"}{"alert"} = undef;
	$result->{"alarm"}{"critic"} = undef;

	#重组巡检结果拼接为字符串
	my $origin = join("\n", @{$rev});
	$result->{"origin"} = $origin;

	#快速检查是否有未注册的模块
	my $ret = (grep {/Unregistered/} @{$rev}) ? 1 : 0;

	#设备健康检查
	$result->{"health"} = "PASS";
	return $result unless $ret;

	#遍历巡检结果以捕捉异常模块信息
	foreach (@{$rev}) {
		if (/(?<alarm>Unregistered)/) {
			#将巡检结果命令行分割为字符串数组
			my @info = split(/\s+/, $_);

			#母板默认显示为-
			$info[1] = "main_board" if $info[1] eq '-';

			#写入异常状态
			my $alarm = lc($+{alarm});
			$result->{"alarm"}{"critic"} = $info[1] . " is $alarm";

			#监控检查状态
			$result->{"health"} = "FAIL";
			last;
		}
	}
	#返回巡检结果
	return $result;
}


#-------------------------------------------------------------------------
# 网络设备-硬件运行状态巡检 -- 电源模块检查
#-------------------------------------------------------------------------
sub power_status {
	#接收巡检命令行输出
	my $rev = shift;
	#如果没匹配到巡检输出直接返回
	return unless $rev;

	#模块巡检标量
	my $result;
	#初始化巡检结果告警项
	$result->{"alarm"}{"info"} = undef;
	$result->{"alarm"}{"alert"} = undef;
	$result->{"alarm"}{"critic"} = undef;

	#重组巡检结果拼接为字符串
	my $origin = join("\n", @{$rev});
	$result->{"origin"} = $origin;

	#快速检查是否有未注册的模块
	my $ret = (grep {/NotSupply/i} @{$rev}) ? 1 : 0;

	#设备健康检查
	$result->{"health"} = "PASS";
	return $result unless $ret;

	#遍历巡检结果以捕捉异常模块信息
	foreach (@{$rev}) {
		if (/(?<alarm>NotSupply)/i) {
			#将巡检结果命令行分割为字符串数组
			my @info = split(/\s+/, $_);

			#写入异常状态
			my $alarm = lc($+{alarm});
			$result->{"alarm"}{"critic"} = $info[1] . " is $alarm";

			#监控检查状态
			$result->{"health"} = "FAIL";
			last;
		}
	}
	#返回巡检结果
	return $result;
}

#-------------------------------------------------------------------------
# 网络设备-硬件运行状态巡检 -- 风扇模块检查
#-------------------------------------------------------------------------
sub fan_status {
	#接收巡检命令行输出
	my $rev = shift;
	#如果没匹配到巡检输出直接返回
	return unless $rev;

	#模块巡检标量
	my $result;
	#初始化巡检结果告警项
	$result->{"alarm"}{"info"} = undef;
	$result->{"alarm"}{"alert"} = undef;
	$result->{"alarm"}{"critic"} = undef;

	#重组巡检结果拼接为字符串
	my $origin = join("\n", @{$rev});
	$result->{"origin"} = $origin;

	#快速检查是否有未注册的模块
	my $ret = (grep {/Abnormal/i} @{$rev}) ? 1 : 0;

	#设备健康检查
	$result->{"health"} = "PASS";
	return $result unless $ret;

	#遍历巡检结果以捕捉异常模块信息
	foreach (@{$rev}) {
		if (/(?<alarm>Abnormal)/i) {
			#将巡检结果命令行分割为字符串数组
			my @info = split(/\s+/, $_);
			
			#写入异常状态
			my $alarm = lc($+{alarm});
			$result->{"alarm"}{"critic"} = $info[1] . " is $alarm";

			#监控检查状态
			$result->{"health"} = "FAIL";
			last;
		}
	}
	#返回巡检结果
	return $result;
}

#-------------------------------------------------------------------------
# 网络设备-硬件运行状态巡检 -- 设备硬件告警检查
#-------------------------------------------------------------------------
sub alarm_status {
	#接收巡检命令行输出
	my $rev = shift;
	#如果没匹配到巡检输出直接返回
	return unless $rev;

	#模块巡检标量
	my $result;
	#初始化巡检结果告警项
	$result->{"alarm"}{"info"} = undef;
	$result->{"alarm"}{"alert"} = undef;
	$result->{"alarm"}{"critic"} = undef;

	#重组巡检结果拼接为字符串
	my $origin = join("\n", @{$rev});
	$result->{"origin"} = $origin;

	#快速检查是否有未注册的模块
	my $ret = (grep {/(Critical)/i} @{$rev}) ? 1 : 0;

	#设备健康检查
	$result->{"health"} = "PASS";
	return $result unless $ret;

	#遍历巡检结果以捕捉异常模块信息,仅关注最新的告警信息
	foreach (@{$rev}) {
		if (/(?<alarm>(Critical))/i) {
			#将巡检结果命令行分割为字符串数组
			my @info = split(/\s+/, $_);
			
			#写入异常状态
			my $alarm = lc($+{alarm});
			$result->{"alarm"}{"critic"} = $_;

			#监控检查状态
			$result->{"health"} = "FAIL";
			last;
		}
	}
	#返回巡检结果
	return $result;
}

#-------------------------------------------------------------------------
# 网络设备-硬件运行状态巡检 -- 硬件温度告警检查
#-------------------------------------------------------------------------
sub temperature_status {
	#接收巡检命令行输出
	my $rev = shift;
	#如果没匹配到巡检输出直接返回
	return unless $rev;

	#模块巡检标量
	my $result;
	#初始化巡检结果告警项
	$result->{"alarm"}{"info"} = undef;
	$result->{"alarm"}{"alert"} = undef;
	$result->{"alarm"}{"critic"} = undef;

	#重组巡检结果拼接为字符串
	my $origin = join("\n", @{$rev});
	$result->{"origin"} = $origin;

	#快速检查是否有未注册的模块
	my $ret = (grep {/(Abnormal)/i} @{$rev}) ? 1 : 0;

	#设备健康检查
	$result->{"health"} = "PASS";
	return $result unless $ret;

	#遍历巡检结果以捕捉异常模块信息,仅关注最新的告警信息
	foreach (@{$rev}) {
		if (/(?<alarm>(Abnormal))/i) {
			#将巡检结果命令行分割为字符串数组
			my @info = split(/\s+/, $_);
			
			#写入异常状态
			my $alarm = lc($+{alarm});
			$result->{"alarm"}{"critic"} = "temperature is high ". "($info[6])";

			#监控检查状态
			$result->{"health"} = "FAIL";
			last;
		}
	}
	#返回巡检结果
	return $result;
}

#-------------------------------------------------------------------------
# 网络设备-硬件运行状态巡检 -- CPU利用率检查
#-------------------------------------------------------------------------
sub cpu_status {
	#接收巡检命令行输出
	my $rev = shift;
	#如果没匹配到巡检输出直接返回
	return unless $rev;

	#模块巡检标量
	my $result;
	#初始化巡检结果告警项
	$result->{"alarm"}{"info"} = undef;
	$result->{"alarm"}{"alert"} = undef;
	$result->{"alarm"}{"critic"} = undef;

	#重组巡检结果拼接为字符串
	my $origin = join("\n", @{$rev});
	$result->{"origin"} = $origin;

	#遍历巡检结果以捕捉异常模块信息,仅关注最新的告警信息
	foreach (@{$rev}) {
		if (/CPU Using Percentage/i) {
			#将巡检结果命令行分割为字符串数组
			my @info = map { s/(^\s+|\s+$)//r} split(/\:/, $_);
			#将CPU运行利用率取出
			my $usage = $info[1];
			#处理利用率指标前后空白字符串
			$usage =~ s/(^\s+|\s+$)//;
			#去除%
			$usage =~ s/\%//;
			
			#写入异常状态
			$result->{"alarm"}{"critic"} = "cpu usage is high: ". "$info[1]\%" if $usage > 60;

			#监控检查状态
			$result->{"health"} = "FAIL";
		}
		elsif (/Max CPU Usage \:/i) {
			#将巡检结果命令行分割为字符串数组
			my @info = map { s/(^\s+|\s+$)//r} split(/\:/, $_);
			#将历史CPU MAX 运行利用率取出
			my $max_usage = $info[1];
			#处理利用率指标前后空白字符串
			$max_usage =~ s/(^\s+|\s+$)//;
			#去除%
			$max_usage =~ s/\%//;
			
			#写入异常状态
			$result->{"alarm"}{"alert"} = "cpu history usage is high: ". "$info[1]\%" if $max_usage > 85;

			#监控检查状态
			$result->{"health"} = "FAIL" if $max_usage > 85;
			last;
		}
	}
	#返回巡检结果
	return $result;
}

#-------------------------------------------------------------------------
# 网络设备-硬件运行状态巡检 -- 内存利用率检查
#-------------------------------------------------------------------------
sub memory_status {
	#接收巡检命令行输出
	my $rev = shift;
	#如果没匹配到巡检输出直接返回
	return unless $rev;

	#模块巡检标量
	my $result;
	#初始化巡检结果告警项
	$result->{"alarm"}{"info"} = undef;
	$result->{"alarm"}{"alert"} = undef;
	$result->{"alarm"}{"critic"} = undef;

	#重组巡检结果拼接为字符串
	my $origin = join("\n", @{$rev});
	$result->{"origin"} = $origin;

	#遍历巡检结果以捕捉异常模块信息,仅关注最新的告警信息
	foreach (@{$rev}) {
		if (/Memory Using Percentage/i) {
			#将巡检结果命令行分割为字符串数组
			my @info = map { s/(^\s+|\s+$)//r} split(/\:/, $_);
			#取出内存利用率
			my $usage = $info[1];
			#处理利用率指标前后空白字符串
			$usage =~ s/(^\s+|\s+$)//;
			#去除%字符串
			$usage =~ s/\%//;

			#写入异常状态
			$result->{"alarm"}{"critic"} = "memory usage is high: ". $usage if $usage > 75;

			#监控检查状态
			$result->{"health"} = "FAIL";
		}
	}
	#返回巡检结果
	return $result;
}

#-------------------------------------------------------------------------
# 网络设备-硬件运行状态巡检 -- NTP同步状态检查
#-------------------------------------------------------------------------
sub ntp_status {
	#接收巡检命令行输出
	my $rev = shift;
	#如果没匹配到巡检输出直接返回
	return unless $rev;

	#模块巡检标量
	my $result;
	#初始化巡检结果告警项
	$result->{"alarm"}{"info"} = undef;
	$result->{"alarm"}{"alert"} = undef;
	$result->{"alarm"}{"critic"} = undef;

	#重组巡检结果拼接为字符串
	my $origin = join("\n", @{$rev});
	$result->{"origin"} = $origin;

	#遍历巡检结果以捕捉异常模块信息,仅关注最新的告警信息
	foreach (@{$rev}) {
		if (/synchronization state/i) {
			#将巡检结果命令行分割为字符串数组
			my @info = map { s/(^\s+|\s+$)//r} split(/\:/, $_);
			#取出内存利用率
			my $status = $info[1];
			#处理利用率指标前后空白字符串
			$status =~ s/(^\s+|\s+$)//;

			#写入异常状态
			my $alarm = lc($+{alarm});
			$result->{"alarm"}{"alert"} = "ntp not synchronized" if ($status =~ /not/i);

			#监控检查状态
			$result->{"health"} = "FAIL";
		}
	}
	#返回巡检结果
	return $result;
}

#-------------------------------------------------------------------------
# 网络设备-硬件运行状态巡检 -- VRRP状态检查
#-------------------------------------------------------------------------
sub vrrp_status {
	#接收巡检命令行输出
	my $rev = shift;
	#如果没匹配到巡检输出直接返回
	return unless $rev;

	#模块巡检标量
	my $result;
	#初始化巡检结果告警项
	$result->{"alarm"}{"info"} = undef;
	$result->{"alarm"}{"alert"} = undef;
	$result->{"alarm"}{"critic"} = undef;

	#重组巡检结果拼接为字符串
	my $origin = join("\n", @{$rev});
	$result->{"origin"} = $origin;

	#快速检查是否有未注册的模块
	my $ret = (grep {/Info(.*?)exist/i} @{$rev}) ? 1 : 0;
	#巡检结果命令行输出不存在则跳过
	$result->{"health"} = "PASS";
	return $result if $ret;

	#遍历巡检结果以捕捉异常模块信息,仅关注最新的告警信息
	foreach (@{$rev}) {
		if (/init/i) {
			#将巡检结果命令行分割为字符串数组
			my @info = map { s/(^\s+|\s+$)//r} split(/\:/, $_);
			#取出内存利用率
			my $status = $info[1];
			#处理利用率指标前后空白字符串
			$status =~ s/(^\s+|\s+$)//;

			#写入异常状态
			my $alarm = lc($+{alarm});
			$result->{"alarm"}{"alert"} = "vrrp not synchronized" if ($status =~ /not/i);

			#监控检查状态
			$result->{"health"} = "FAIL";
		}
	}
	#返回巡检结果
	return $result;
}

#-------------------------------------------------------------------------
# 网络设备-硬件运行状态巡检 -- ETH-TRUNK状态检查
#-------------------------------------------------------------------------
sub eth_trunk_status {
	#接收巡检命令行输出
	my $rev = shift;
	#如果没匹配到巡检输出直接返回
	return unless $rev;

	#模块巡检标量
	my $result;
	#初始化巡检结果告警项
	$result->{"alarm"}{"info"} = undef;
	$result->{"alarm"}{"alert"} = undef;
	$result->{"alarm"}{"critic"} = undef;

	#重组巡检结果拼接为字符串
	my $origin = join("\n", @{$rev});
	$result->{"origin"} = $origin;

	#快速检查是否有未注册的模块
	my $ret = (grep {/Info(.*?)exist/i} @{$rev}) ? 1 : 0;
	#巡检结果命令行输出不存在则跳过
	$result->{"health"} = "PASS";
	return $result if $ret;

	#遍历巡检结果以捕捉异常模块信息,仅关注最新的告警信息
	foreach (@{$rev}) {
		if (/init/i) {
			#将巡检结果命令行分割为字符串数组
			my @info = map { s/(^\s+|\s+$)//r} split(/\:/, $_);
			#取出内存利用率
			my $status = $info[1];
			#处理利用率指标前后空白字符串
			$status =~ s/(^\s+|\s+$)//;

			#写入异常状态
			my $alarm = lc($+{alarm});
			$result->{"alarm"}{"alert"} = "eth-trunk is Abnormal" if ($status =~ /not/i);

			#监控检查状态
			$result->{"health"} = "FAIL";
		}
	}
	#返回巡检结果
	return $result;
}

#-------------------------------------------------------------------------
# 网络设备-硬件运行状态巡检 -- ETH-TRUNK状态检查
#-------------------------------------------------------------------------
sub eth_trunk_status {
	#接收巡检命令行输出
	my $rev = shift;
	#如果没匹配到巡检输出直接返回
	return unless $rev;

	#模块巡检标量
	my $result;
	#初始化巡检结果告警项
	$result->{"alarm"}{"info"} = undef;
	$result->{"alarm"}{"alert"} = undef;
	$result->{"alarm"}{"critic"} = undef;

	#重组巡检结果拼接为字符串
	my $origin = join("\n", @{$rev});
	$result->{"origin"} = $origin;

	#快速检查是否有未注册的模块
	my $ret = (grep {/Info(.*?)exist/i} @{$rev}) ? 1 : 0;
	#巡检结果命令行输出不存在则跳过
	$result->{"health"} = "PASS";
	return $result if $ret;

	#遍历巡检结果以捕捉异常模块信息,仅关注最新的告警信息
	foreach (@{$rev}) {
		if (/init/i) {
			#将巡检结果命令行分割为字符串数组
			my @info = map { s/(^\s+|\s+$)//r} split(/\:/, $_);
			#取出内存利用率
			my $status = $info[1];
			#处理利用率指标前后空白字符串
			$status =~ s/(^\s+|\s+$)//;

			#写入异常状态
			my $alarm = lc($+{alarm});
			$result->{"alarm"}{"alert"} = "eth-trunk is Abnormal" if ($status =~ /not/i);

			#监控检查状态
			$result->{"health"} = "FAIL";
		}
	}
	#返回巡检结果
	return $result;
}
1; # End of Huawei::Healthchecker
