# # Copyright 2018 Centreon (http://www.centreon.com/) # # Centreon is a full-fledged industry-strength solution that meets # the needs in IT infrastructure and application monitoring for # service performance. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # package database::oracle::sqlpluscmd; use strict; use warnings; use centreon::plugins::misc; use Digest::MD5 qw(md5_hex); use File::Temp qw(tempfile); use Data::Dumper; sub new { my ($class, %options) = @_; my $self = {}; bless $self, $class; # $options{options} = options object # $options{output} = output object # $options{exit_value} = integer # $options{noptions} = integer if (!defined($options{output})) { print "Class sqlpluscmd: Need to specify 'output' argument.\n"; exit 3; } if (!defined($options{options})) { $options{output}->add_option_msg(short_msg => "Class sqlpluscmd: Need to specify 'options' argument."); $options{output}->option_exit(); } if (!defined($options{noptions})) { $options{options}->add_options( arguments => { "sqlplus-cmd:s" => { name => 'sqlplus_cmd'}, "oracle-home:s" => { name => 'oracle_home' }, "tnsadmin-home:s" => { name => 'tnsadmin_home' }, "tnsnames-sid:s" => { name => 'tnsnames_sid'}, "username:s" => { name => 'username' }, "password:s" => { name => 'password' }, "local-connexion" => { name => 'local_connexion', default=>0 }, "sysdba" => { name => 'sysdba', default=>0 }, "sql-errors-exit:s" => { name => 'sql_errors_exit', default => 'unknown' }, "tempdir:s" => { name => 'tempdir', default => '/tmp' }, } ); } $options{options}->add_help(package => __PACKAGE__, sections => 'sqlpluscmd OPTIONS', once => 1); $self->{output} = $options{output}; $self->{mode} = $options{mode}; $self->{args} = undef; $self->{stdout} = undef; $self->{columns} = undef; $self->{version} = undef; $self->{sid} = undef; $self->{oracle_home} = undef; $self->{tnsadmin_home} = undef; $self->{local_connexion} = undef; return $self; } # Method to manage multiples sub set_options { my ($self, %options) = @_; # options{options_result} $self->{option_results} = $options{option_results}; } # Method to manage multiples sub set_defaults { my ($self, %options) = @_; # options{default} # Manage default value foreach (keys %{$options{default}}) { if ($_ eq $self->{mode}) { for (my $i = 0; $i < scalar(@{$options{default}->{$_}}); $i++) { foreach my $opt (keys %{$options{default}->{$_}[$i]}) { if (!defined($self->{option_results}->{$opt}[$i])) { $self->{option_results}->{$opt}[$i] = $options{default}->{$_}[$i]->{$opt}; } } } } } } sub check_options { my ($self, %options) = @_; # return 1 = ok still data_source # return 0 = no data_source left $self->{sid} = defined($self->{option_results}->{sid}[0]) ? $self->{option_results}->{sid}[0]: $self->{option_results}->{tnsnames_sid}; $self->{oracle_home} = defined($self->{option_results}->{oracle_home}) ? $self->{option_results}->{oracle_home} : $ENV{'ORACLE_HOME'}; $self->{tnsadmin_home} = defined($self->{option_results}->{tnsadmin_home}) ? $self->{option_results}->{tnsadmin_home} : $ENV{'TNSADMIN'}; $self->{local_connexion} = $self->{option_results}->{local_connexion}; $self->{sqlplus_cmd} = $self->{option_results}->{sqlplus_cmd}; $self->{output}->output_add(long_msg=>"*** DEBUG MODE****\n"); $self->{output}->output_add(long_msg=>Dumper($self->{option_results}));; # check the SID prerequisite option if (!defined($self->{sid}) || $self->{sid} eq '') { $self->{output}->add_option_msg(short_msg => "Need to specify sid argument."); $self->{output}->option_exit(exit_litteral => $self->{option_results}->{sql_errors_exit}); } # check the ORACLE_HOME variable if (!defined($self->{oracle_home}) || $self->{oracle_home} eq '') { $self->{output}->add_option_msg(short_msg => "Need to specify oracle-home argument."); $self->{output}->option_exit(exit_litteral => $self->{option_results}->{sql_errors_exit}); } # construct the TNSADMIN variable if not available if(!defined($self->{tnsadmin_home})) { $self->{tnsadmin_home} = $self->{oracle_home}."/network/admin"; } # check the SQLPLUS command to use if(!$self->{sqlplus_cmd}) { $self->{sqlplus_cmd} = $self->{oracle_home}."/bin/sqlplus"; } $self->{args} = ['-L', '-S']; my $connection_string = ""; if($self->{option_results}->{sysdba} == 1) { $self->{output}->output_add(long_msg=>"*** SYDBA MODE****\n"); $connection_string="/ as sysdba"; $self->{local_connexion} = 1; } elsif(defined($self->{option_results}->{username}) && defined($self->{option_results}->{password})) { $connection_string=$self->{option_results}->{username}."/".$self->{option_results}->{password}; } else { $self->{output}->add_option_msg(short_msg => "Need to specify username/password arguments or sysdba option."); $self->{output}->option_exit(exit_litteral => $self->{option_results}->{sql_errors_exit}); } if($self->{local_connexion} == 0) { if(defined($self->{option_results}->{hostname})) { my $port = defined($self->{option_results}->{port}) ? $self->{option_results}->{port}[0] : 1521; $connection_string .= "\@//".$self->{option_results}->{hostname}[0].":".$port."/".$self->{sid}; } else { $connection_string .= "\@".$self->{sid}; } } else { $self->{output}->output_add(long_msg=>"*** LOCAL CONNEXION MODE****\n"); $ENV{ORACLE_SID} = $self->{sid}; } # register a false data_source to be compliant with tnsping mode $self->{data_source}="sid=".$self->{sid}; push @{$self->{args}}, $connection_string; # set oracle env variable $ENV{ORACLE_HOME} = $self->{oracle_home}; $ENV{TNSADMIN} = $self->{tnsadmin_home}; if (defined($self->{option_results}->{sid})) { return 0; } return 1; } sub is_version_minimum { my ($self, %options) = @_; # $options{version} = string version to check my @version_src = split /\./, $self->{version}; my @versions = split /\./, $options{version}; for (my $i = 0; $i < scalar(@versions); $i++) { return 1 if ($versions[$i] eq 'x'); return 1 if (!defined($version_src[$i])); $version_src[$i] =~ /^([0-9]*)/; next if ($versions[$i] == int($1)); return 0 if ($versions[$i] > int($1)); return 1 if ($versions[$i] < int($1)); } return 1; } sub get_id { my ($self, %options) = @_; my $msg = $self->{sid}; return $msg; } sub get_unique_id4save { my ($self, %options) = @_; my $msg = $self->{sid}; return md5_hex($msg); } sub quote { my $self = shift; return undef; } sub command_execution { my ($self, %options) = @_; my ($fh, $tempfile) = tempfile( DIR => $self->{option_results}->{tempdir}, SUFFIX => ".sql", UNLINK => 1 ); print $fh "set echo off -- set heading off set feedback off set linesize 16000 set pagesize 50000 set colsep '#&!#' set numwidth 15 $options{request}; exit;"; $self->{output}->output_add(long_msg=>"*** COMMAND: ".$self->{sqlplus_cmd}.' '.join(' ',(@{$self->{args}},'@', $tempfile))."\n"); $self->{output}->output_add(long_msg=>"*** REQUEST: ".$options{request}."\n"); my ($lerror, $stdout, $exit_code) = centreon::plugins::misc::backtick( command => $self->{sqlplus_cmd}, arguments => [@{$self->{args}}, '@', $tempfile], timeout => 30, wait_exit => 1, redirect_stderr => 1 ); $self->{output}->output_add(long_msg=>"REQ. STDOUT: '$stdout'\n"); $self->{output}->output_add(long_msg=>"REQ. EXIT_CODE: $exit_code\n"); # search oracle error lines $exit_code = -1 if($stdout =~ /^(ORA\-\d+|TNS\-\d+|SP\d\-\d+)/); if ($exit_code <= -1000) { if ($exit_code == -1000) { $self->{output}->output_add(severity => 'UNKNOWN', short_msg => $stdout); } $self->{output}->display(); $self->{output}->exit(); } return ($exit_code, $stdout); } # Connection initializer sub connect { my ($self, %options) = @_; my $dontquit = (defined($options{dontquit}) && $options{dontquit} == 1) ? 1 : 0; (my $exit_code, $self->{stdout}) = $self->command_execution(request => "select version from v\$instance"); if ($exit_code != 0) { if ($dontquit == 0) { $self->{output}->add_option_msg(short_msg => "Cannot connect: " . $self->{stdout}); $self->{output}->option_exit(exit_litteral => $self->{option_results}->{sql_errors_exit}); } return (-1, "Cannot connect: " . $self->{stdout}); } $self->{version} = $self->fetchrow_array(); $self->{output}->output_add(long_msg=>"VERSION: ".$self->{version}."\n"); return 0; } sub fetchall_arrayref { my ($self, %options) = @_; my $array_ref = []; if($self->{stdout} eq '') { $self->{output}->output_add(long_msg=>"fetchall_arrayref: no data returned (no rows selected)\n"); return $array_ref; } if (!defined($self->{columns})) { $self->{stdout} =~ s/^\s*\n(.*?)(\n|$)//; my $line = $1; $self->{output}->output_add(long_msg=>"fetchall_arrayref COLUMNS: $line\n") if(defined($line)); @{$self->{columns}} = split(/#&!#/, $line); map { s/^\s+|\s+$//g; } @{$self->{columns}}; $self->{stdout} =~ s/[\-#&!]+(\n|$)//; } foreach (split /\n/, $self->{stdout}) { my $line = $_; $line =~ s/^\s+|\s+$//g; $line =~ s/#&!#\s+/#&!#/g; $line =~ s/\s+#&!#/#&!#/g; $self->{output}->output_add(long_msg=>"fetchall_arrayref VALUE: ".$line."\n"); push @$array_ref, [map({ s/\\n/\x{0a}/g; s/\\t/\x{09}/g; s/\\/\x{5c}/g; $_; } split(/#&!#/, $line))]; } return $array_ref; } sub fetchrow_array { my ($self, %options) = @_; my @array_result = (); if($self->{stdout} eq '') { $self->{output}->output_add("fetchrow_array: no data returned (no rows selected)\n"); return @array_result; } if (!defined($self->{columns})) { $self->{stdout} =~ s/^\s*\n(.*?)(\n|$)//; my $line = $1; $self->{output}->output_add(long_msg=>"fetchrow_array COLUMNS: $line\n"); @{$self->{columns}} = split(/#&!#/, $line); map { s/^\s+|\s+$//g; } @{$self->{columns}}; $self->{stdout} =~ s/[\-#&!]+(\n|$)//; } $self->{output}->output_add(long_msg=>"fetchrow_array STDOUT: '".$self->{stdout}."'\n"); if (($self->{stdout} =~ s/^(.*?)(\n|$)//)) { my $line = $1; $self->{output}->output_add(long_msg=>"fetchrow_array VALUE: '".$line."'\n"); push @array_result, map({ s/\\n/\x{0a}/g; s/\\t/\x{09}/g; s/\\/\x{5c}/g; $_; } split(/#&!#/, $line)); map { s/^\s+|\s+$//g; } @array_result; $self->{output}->output_add(long_msg=>"ARRAY: ".Dumper(@array_result)."\n"); } $self->{output}->output_add(long_msg=>"RETURN: ".Dumper(@array_result)."\n"); return scalar(@array_result) == 1 ? $array_result[0] : @array_result ; } sub fetchrow_hashref { my ($self, %options) = @_; my $array_result = undef; if($self->{stdout} eq '') { $self->{output}->output_add(long_msg=>"fetchrow_hashref: no data returned (no rows selected)\n"); return $array_result; } if (!defined($self->{columns})) { $self->{stdout} =~ s/^\s*\n(.*?)(\n|$)//; my $line = $1; $self->{output}->output_add(long_msg=>"fetchrow_hashref COLUMNS: $line\n"); @{$self->{columns}} = split(/#&!#/, $line); map { s/^\s+|\s+$//g; } @{$self->{columns}}; $self->{stdout} =~ s/[\-#&!]+(\n|$)//; } if ($self->{stdout} ne '' && $self->{stdout} =~ s/^(.*?)(\n|$)//) { my $line = $1; $self->{output}->output_add(long_msg=>"fetchrow_hashref VALUE: ".$line."\n"); $array_result = {}; my @values = split(/#&!#/, $line); for (my $i = 0; $i < scalar(@values); $i++) { my $value = $values[$i]; $value =~ s/^\s+|\s+$//g; $value =~ s/\\n/\x{0a}/g; $value =~ s/\\t/\x{09}/g; $value =~ s/\\/\x{5c}/g; $self->{output}->output_add(long_msg=>"fetchrow_hashref RES: '".$self->{columns}[$i]."' = '$value'\n"); $array_result->{$self->{columns}[$i]} = $value; } } return $array_result; } sub query { my ($self, %options) = @_; $self->{columns} = undef; (my $exit_code, $self->{stdout}) = $self->command_execution(request => $options{query}); if ($exit_code != 0) { $self->{output}->add_option_msg(short_msg => "Cannot execute query:\nQuery:".$options{query}."\nOutput: ". $self->{stdout}); $self->{output}->option_exit(exit_litteral => $self->{option_results}->{sql_errors_exit}); } } 1; __END__ =head1 NAME sqlpluscmd global =head1 SYNOPSIS sqlpluscmd class =head1 sqlpluscmd OPTIONS =over 8 =item B<--sqlplus-cmd> sqlplus command (Default: 'sqlplus'). =item B<--oracle-home> Oracle Database Server Home. =item B<--tnsadmin-home> Oracle TNS Admin Home. Where to locate tnsnames.ora file (default: ${ORACLE_HOME}/network/admin) =item B<--tnsnames-sid> Oracle SID defined in tnsnames.ora. =item B<--username> Database username. =item B<--password> Database password. =item B<--sysdba> Use a sysdba connexion, need to be execute under the oracle local user on the server, and use a local connexion. =item B<--local-connexion> Use a local connexion, don't need listener. =item B<--sql-errors-exit> Exit code for DB Errors (default: unknown) =back =head1 DESCRIPTION B. =cut