1# -- 2# Copyright (C) 2001-2020 OTRS AG, https://otrs.com/ 3# -- 4# This software comes with ABSOLUTELY NO WARRANTY. For details, see 5# the enclosed file COPYING for license information (GPL). If you 6# did not receive this file, see https://www.gnu.org/licenses/gpl-3.0.txt. 7# -- 8 9package Kernel::System::Console::Command::Admin::Article::StorageSwitch; 10 11use strict; 12use warnings; 13 14use parent qw(Kernel::System::Console::BaseCommand); 15 16use Time::HiRes qw(usleep); 17 18our @ObjectDependencies = ( 19 'Kernel::Config', 20 'Kernel::System::DateTime', 21 'Kernel::System::PID', 22 'Kernel::System::Ticket', 23); 24 25sub Configure { 26 my ( $Self, %Param ) = @_; 27 28 $Self->Description('Migrate article files from one storage backend to another on the fly.'); 29 $Self->AddOption( 30 Name => 'target', 31 Description => "Specify the target backend to migrate to (ArticleStorageDB|ArticleStorageFS).", 32 Required => 1, 33 HasValue => 1, 34 ValueRegex => qr/^(?:ArticleStorageDB|ArticleStorageFS)$/smx, 35 ); 36 $Self->AddOption( 37 Name => 'tickets-closed-before-date', 38 Description => "Only process tickets closed before given ISO date.", 39 Required => 0, 40 HasValue => 1, 41 ValueRegex => qr/^\d{4}-\d{2}-\d{2}[ ]\d{2}:\d{2}:\d{2}$/smx, 42 ); 43 $Self->AddOption( 44 Name => 'tickets-closed-before-days', 45 Description => "Only process tickets closed more than ... days ago.", 46 Required => 0, 47 HasValue => 1, 48 ValueRegex => qr/^\d+$/smx, 49 ); 50 $Self->AddOption( 51 Name => 'tolerant', 52 Description => "Continue after failures.", 53 Required => 0, 54 HasValue => 0, 55 ); 56 $Self->AddOption( 57 Name => 'micro-sleep', 58 Description => "Specify microseconds to sleep after every ticket to reduce system load (e.g. 1000).", 59 Required => 0, 60 HasValue => 1, 61 ValueRegex => qr/^\d+$/smx, 62 ); 63 $Self->AddOption( 64 Name => 'force-pid', 65 Description => "Start even if another process is still registered in the database.", 66 Required => 0, 67 HasValue => 0, 68 ); 69 70 my $Name = $Self->Name(); 71 72 $Self->AdditionalHelp(<<"EOF"); 73The <green>$Name</green> command migrates article data from one storage backend to another on the fly, for example from DB to FS: 74 75 <green>otrs.Console.pl $Self->{Name} --target ArticleStorageFS</green> 76 77You can specify limits for the tickets migrated with <yellow>--tickets-closed-before-date</yellow> and <yellow>--tickets-closed-before-days</yellow>. 78 79To reduce load on the database for a running system, you can use the <yellow>--micro-sleep</yellow> parameter. The command will pause for the specified amount of microseconds after each ticket. 80 81 <green>otrs.Console.pl $Self->{Name} --target ArticleStorageFS --micro-sleep 1000</green> 82EOF 83 return; 84} 85 86sub PreRun { 87 my ($Self) = @_; 88 89 my $PIDCreated = $Kernel::OM->Get('Kernel::System::PID')->PIDCreate( 90 Name => $Self->Name(), 91 Force => $Self->GetOption('force-pid'), 92 TTL => 60 * 60 * 24 * 3, 93 ); 94 if ( !$PIDCreated ) { 95 my $Error = "Unable to register the process in the database. Is another instance still running?\n"; 96 $Error .= "You can use --force-pid to override this check.\n"; 97 die $Error; 98 } 99 100 return; 101} 102 103sub Run { 104 my ($Self) = @_; 105 106 # disable ticket events 107 $Kernel::OM->Get('Kernel::Config')->{'Ticket::EventModulePost'} = {}; 108 109 # extended input validation 110 my %SearchParams; 111 112 if ( $Self->GetOption('tickets-closed-before-date') ) { 113 %SearchParams = ( 114 StateType => 'Closed', 115 TicketCloseTimeOlderDate => $Self->GetOption('tickets-closed-before-date'), 116 ); 117 } 118 elsif ( $Self->GetOption('tickets-closed-before-days') ) { 119 my $Seconds = $Self->GetOption('tickets-closed-before-days') * 60 * 60 * 24; 120 121 my $OlderDTObject = $Kernel::OM->Create('Kernel::System::DateTime'); 122 $OlderDTObject->Subtract( Seconds => $Seconds ); 123 124 %SearchParams = ( 125 StateType => 'Closed', 126 TicketCloseTimeOlderDate => $OlderDTObject->ToString(), 127 ); 128 } 129 130 # If Archive system is enabled, take into account archived tickets as well. 131 # See bug#13945 (https://bugs.otrs.org/show_bug.cgi?id=13945). 132 if ( $Kernel::OM->Get('Kernel::Config')->{'Ticket::ArchiveSystem'} ) { 133 $SearchParams{ArchiveFlags} = [ 'y', 'n' ]; 134 } 135 136 my $TicketObject = $Kernel::OM->Get('Kernel::System::Ticket'); 137 138 my @TicketIDs = $TicketObject->TicketSearch( 139 %SearchParams, 140 Result => 'ARRAY', 141 OrderBy => 'Up', 142 Limit => 1_000_000_000, 143 UserID => 1, 144 Permission => 'ro', 145 ); 146 147 my $Count = 0; 148 my $CountTotal = scalar @TicketIDs; 149 150 my $Target = $Self->GetOption('target'); 151 my %Target2Source = ( 152 ArticleStorageFS => 'ArticleStorageDB', 153 ArticleStorageDB => 'ArticleStorageFS', 154 ); 155 156 my $MicroSleep = $Self->GetOption('micro-sleep'); 157 my $Tolerant = $Self->GetOption('tolerant'); 158 159 TICKETID: 160 for my $TicketID (@TicketIDs) { 161 162 $Count++; 163 164 $Self->Print("$Count/$CountTotal (TicketID:$TicketID)\n"); 165 166 my $Success = $TicketObject->TicketArticleStorageSwitch( 167 TicketID => $TicketID, 168 Source => $Target2Source{$Target}, 169 Destination => $Target, 170 UserID => 1, 171 ); 172 173 return $Self->ExitCodeError() if !$Tolerant && !$Success; 174 175 Time::HiRes::usleep($MicroSleep) if ($MicroSleep); 176 } 177 178 $Self->Print("<green>Done.</green>\n"); 179 180 return $Self->ExitCodeOk(); 181 182} 183 184sub PostRun { 185 my ($Self) = @_; 186 187 return $Kernel::OM->Get('Kernel::System::PID')->PIDDelete( Name => $Self->Name() ); 188} 189 1901; 191