Files
scripts/Perl OTRS/Kernel/System/SupportDataCollector.pm
2024-10-14 00:08:40 +02:00

476 lines
14 KiB
Perl

# --
# Copyright (C) 2001-2019 OTRS AG, https://otrs.com/
# --
# This software comes with ABSOLUTELY NO WARRANTY. For details, see
# the enclosed file COPYING for license information (GPL). If you
# did not receive this file, see https://www.gnu.org/licenses/gpl-3.0.txt.
# --
package Kernel::System::SupportDataCollector;
use strict;
use warnings;
use File::Basename;
use Kernel::System::WebUserAgent;
our @ObjectDependencies = (
'Kernel::Config',
'Kernel::System::Cache',
'Kernel::System::Encode',
'Kernel::System::JSON',
'Kernel::System::Log',
'Kernel::System::Main',
'Kernel::System::SystemData',
);
=head1 NAME
Kernel::System::SupportDataCollector - system data collector
=head1 DESCRIPTION
All stats functions.
=head1 PUBLIC INTERFACE
=head2 new()
Don't use the constructor directly, use the ObjectManager instead:
my $SupportDataCollectorObject = $Kernel::OM->Get('Kernel::System::SupportDataCollector');
=cut
sub new {
my ( $Type, %Param ) = @_;
# allocate new hash ref to object
my $Self = {};
bless( $Self, $Type );
return $Self;
}
=head2 Collect()
collect system data
my %Result = $SupportDataCollectorObject->Collect(
UseCache => 1, # (optional) to get data from cache if any
WebTimeout => 60, # (optional)
Debug => 1, # (optional)
Hostname => 'my.test.host:8080' # (optional, for testing purposes)
);
returns in case of error
(
Success => 0,
ErrorMessage => '...',
)
otherwise
(
Success => 1,
Result => [
{
Identifier => 'Kernel::System::SupportDataCollector::OTRS::Version',
DisplayPath => 'OTRS',
Status => $StatusOK,
Label => 'OTRS Version'
Value => '3.3.2',
Message => '',
},
{
Identifier => 'Kernel::System::SupportDataCollector::Apache::mod_perl',
DisplayPath => 'OTRS',
Status => $StatusProblem,
Label => 'mod_perl usage'
Value => '0',
Message => 'Please enable mod_perl to speed up OTRS.',
},
{
Identifier => 'Some::Identifier',
DisplayPath => 'SomePath',
Status => $StatusOK,
Label => 'Some Label'
Value => '0',
MessageFormatted => 'Some \n Formatted \n\t Text.',
},
],
)
=cut
sub Collect {
my ( $Self, %Param ) = @_;
my $CacheKey = 'DataCollect';
if ( $Param{UseCache} ) {
my $Cache = $Kernel::OM->Get('Kernel::System::Cache')->Get(
Type => 'SupportDataCollector',
Key => $CacheKey,
);
return %{$Cache} if ref $Cache eq 'HASH';
}
# Data must be collected in a web request context to be able to collect web server data.
# If called from CLI, make a web request to collect the data, but if the data couldn't
# be collected the function runs normal.
if ( !$ENV{GATEWAY_INTERFACE} ) {
my %ResultWebRequest = $Self->CollectByWebRequest(%Param);
return %ResultWebRequest if $ResultWebRequest{Success};
}
# Get the disabled plugins from the config to generate a lookup hash, which can be used to skip these plugins.
my $PluginDisabled = $Kernel::OM->Get('Kernel::Config')->Get('SupportDataCollector::DisablePlugins') || [];
my %LookupPluginDisabled = map { $_ => 1 } @{$PluginDisabled};
# Get the identifier filter blacklist from the config to generate a lookup hash, which can be used to
# filter these identifier.
my $IdentifierFilterBlacklist
= $Kernel::OM->Get('Kernel::Config')->Get('SupportDataCollector::IdentifierFilterBlacklist') || [];
my %LookupIdentifierFilterBlacklist = map { $_ => 1 } @{$IdentifierFilterBlacklist};
# Look for all plug-ins in the FS.
my @PluginFiles = $Kernel::OM->Get('Kernel::System::Main')->DirectoryRead(
Directory => dirname(__FILE__) . "/SupportDataCollector/Plugin",
Filter => "*.pm",
Recursive => 1,
);
# Look for all asynchronous plug-ins in the FS.
my @PluginAsynchronousFiles = $Kernel::OM->Get('Kernel::System::Main')->DirectoryRead(
Directory => dirname(__FILE__) . "/SupportDataCollector/PluginAsynchronous",
Filter => "*.pm",
Recursive => 1,
);
# Merge the both plug-in types together.
my @PluginFilesAll = ( @PluginFiles, @PluginAsynchronousFiles );
my @Result;
# Execute all plug-ins.
PLUGINFILE:
for my $PluginFile (@PluginFilesAll) {
# Convert file name => package name
$PluginFile =~ s{^.*(Kernel/System.*)[.]pm$}{$1}xmsg;
$PluginFile =~ s{/+}{::}xmsg;
next PLUGINFILE if $LookupPluginDisabled{$PluginFile};
if ( !$Kernel::OM->Get('Kernel::System::Main')->Require($PluginFile) ) {
return (
Success => 0,
ErrorMessage => "Could not load $PluginFile!",
);
}
my $PluginObject = $PluginFile->new( %{$Self} );
my %PluginResult = $PluginObject->Run();
if ( !%PluginResult || !$PluginResult{Success} ) {
return (
Success => 0,
ErrorMessage => "Error during execution of $PluginFile: $PluginResult{ErrorMessage}",
);
}
push @Result, @{ $PluginResult{Result} // [] };
}
# Remove the disabled plugins after the execution, because some plugins returns
# more information with a own identifier.
@Result = grep { !$LookupIdentifierFilterBlacklist{ $_->{Identifier} } } @Result;
# Sort the results from the plug-ins by the short identifier.
@Result = sort { $a->{ShortIdentifier} cmp $b->{ShortIdentifier} } @Result;
my %ReturnData = (
Success => 1,
Result => \@Result,
);
# Cache the result only, if the support data were collected in a web request,
# to have all support data in the admin view.
if ( $ENV{GATEWAY_INTERFACE} ) {
$Kernel::OM->Get('Kernel::System::Cache')->Set(
Type => 'SupportDataCollector',
Key => $CacheKey,
Value => \%ReturnData,
TTL => 60 * 10,
);
}
return %ReturnData;
}
sub CollectByWebRequest {
my ( $Self, %Param ) = @_;
# Create a challenge token to authenticate this request without customer/agent login.
# PublicSupportDataCollector requires this ChallengeToken.
my $ChallengeToken = $Kernel::OM->Get('Kernel::System::Main')->GenerateRandomString(
Length => 32,
Dictionary => [ 0 .. 9, 'a' .. 'f' ], # Generate a hexadecimal value.
);
if (
$Kernel::OM->Get('Kernel::System::SystemData')->SystemDataGet( Key => 'SupportDataCollector::ChallengeToken' )
)
{
$Kernel::OM->Get('Kernel::System::SystemData')->SystemDataUpdate(
Key => 'SupportDataCollector::ChallengeToken',
Value => $ChallengeToken,
UserID => 1,
);
}
else {
$Kernel::OM->Get('Kernel::System::SystemData')->SystemDataAdd(
Key => 'SupportDataCollector::ChallengeToken',
Value => $ChallengeToken,
UserID => 1,
);
}
my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
my $Host = $Param{Hostname};
$Host ||= $ConfigObject->Get('SupportDataCollector::HTTPHostname');
if ( !$Host ) {
my $FQDN = $ConfigObject->Get('FQDN');
if ( $FQDN ne 'yourhost.example.com' && gethostbyname($FQDN) ) {
$Host = $FQDN;
}
if ( !$Host && gethostbyname('localhost') ) {
$Host = 'localhost';
}
$Host ||= '127.0.0.1';
}
# If the public interface is proteceted with .htaccess
# we can specify the htaccess login data here,
# this is neccessary for the support data collector.
my $AuthString = '';
my $AuthUser = $ConfigObject->Get('PublicFrontend::AuthUser');
my $AuthPassword = $ConfigObject->Get('PublicFrontend::AuthPassword');
if ( $AuthUser && $AuthPassword ) {
$AuthString = $AuthUser . ':' . $AuthPassword . '@';
}
# Prepare web service config for the internal web request.
my $URL =
$ConfigObject->Get('HttpType')
. '://'
. $AuthString
. $Host
. '/'
. $ConfigObject->Get('ScriptAlias')
. 'public.pl';
my $WebUserAgentObject = Kernel::System::WebUserAgent->new(
Timeout => $Param{WebTimeout} || 20,
);
# Disable webuseragent proxy since the call is sent to self server, see bug#11680.
$WebUserAgentObject->{Proxy} = '';
my %Result = (
Success => 0,
);
# Skip the ssl verification, because this is only a internal web request.
my %Response = $WebUserAgentObject->Request(
Type => 'POST',
URL => $URL,
Data => {
Action => 'PublicSupportDataCollector',
ChallengeToken => $ChallengeToken,
},
SkipSSLVerification => 1,
NoLog => $Param{Debug} ? 0 : 1,
);
if ( $Response{Status} ne '200 OK' ) {
if ( $Param{Debug} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'notice',
Message => "SupportDataCollector - Can't connect to server - $Response{Status}",
);
}
return %Result;
}
# check if we have content as a scalar ref
if ( !$Response{Content} || ref $Response{Content} ne 'SCALAR' ) {
if ( $Param{Debug} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'notice',
Message => "SupportDataCollector - No content received.",
);
}
return %Result;
}
# convert internal used charset
$Kernel::OM->Get('Kernel::System::Encode')->EncodeInput( $Response{Content} );
# Discard HTML responses (error pages etc.).
if ( substr( ${ $Response{Content} }, 0, 1 ) eq '<' ) {
if ( $Param{Debug} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'notice',
Message => "SupportDataCollector - Response looks like HTML instead of JSON.",
);
}
return %Result;
}
# decode JSON data
my $ResponseData = $Kernel::OM->Get('Kernel::System::JSON')->Decode(
Data => ${ $Response{Content} },
);
if ( !$ResponseData || ref $ResponseData ne 'HASH' ) {
if ( $Param{Debug} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "SupportDataCollector - Can't decode JSON: '" . ${ $Response{Content} } . "'!",
);
}
return %Result;
}
return %{$ResponseData};
}
=head2 CollectAsynchronous()
collect asynchronous data (the asynchronous plug-in decide at which place the data will be saved)
my %Result = $SupportDataCollectorObject->CollectAsynchronous();
returns:
%Result = (
Success => 1, # or 0 in case of an error
ErrorMessage => 'some message' # optional (only in case of an error)
);
return
=cut
sub CollectAsynchronous {
my ( $Self, %Param ) = @_;
# Look for all plug-ins in the FS
my @PluginFiles = $Kernel::OM->Get('Kernel::System::Main')->DirectoryRead(
Directory => dirname(__FILE__) . "/SupportDataCollector/PluginAsynchronous",
Filter => "*.pm",
Recursive => 1,
);
# Execute all plug-ins
for my $PluginFile (@PluginFiles) {
# Convert file name => package name
$PluginFile =~ s{^.*(Kernel/System.*)[.]pm$}{$1}xmsg;
$PluginFile =~ s{/+}{::}xmsg;
if ( !$Kernel::OM->Get('Kernel::System::Main')->Require($PluginFile) ) {
return (
Success => 0,
ErrorMessage => "Could not load $PluginFile!",
);
}
my $PluginObject = $PluginFile->new( %{$Self} );
my $Success = $PluginObject->RunAsynchronous();
if ( !$Success ) {
return (
Success => 0,
ErrorMessage => "Error during asynchronous execution of $PluginFile.",
);
}
}
return (
Success => 1,
);
}
=head2 CleanupAsynchronous()
clean-up asynchronous data (the asynchronous plug-in decide for themselves)
my $Success = $SupportDataCollectorObject->CleanupAsynchronous();
=cut
sub CleanupAsynchronous {
my ( $Self, %Param ) = @_;
# Look for all plug-ins in the FS
my @PluginFiles = $Kernel::OM->Get('Kernel::System::Main')->DirectoryRead(
Directory => dirname(__FILE__) . "/SupportDataCollector/PluginAsynchronous",
Filter => "*.pm",
Recursive => 1,
);
# Execute all Plug-ins
PLUGINFILE:
for my $PluginFile (@PluginFiles) {
# Convert file name => package name
$PluginFile =~ s{^.*(Kernel/System.*)[.]pm$}{$1}xmsg;
$PluginFile =~ s{/+}{::}xmsg;
if ( !$Kernel::OM->Get('Kernel::System::Main')->Require($PluginFile) ) {
return (
Success => 0,
ErrorMessage => "Could not load $PluginFile!",
);
}
my $PluginObject = $PluginFile->new( %{$Self} );
next PLUGINFILE if !$PluginFile->can('CleanupAsynchronous');
$PluginObject->CleanupAsynchronous();
}
return 1;
}
=head1 TERMS AND CONDITIONS
This software is part of the OTRS project (L<https://otrs.org/>).
This software comes with ABSOLUTELY NO WARRANTY. For details, see
the enclosed file COPYING for license information (GPL). If you
did not receive this file, see L<https://www.gnu.org/licenses/gpl-3.0.txt>.
=cut
1;