WIP - prepare cron cleanup + first tests

This commit is contained in:
odain
2026-03-13 11:04:33 +01:00
parent b7b00b1685
commit 4853a4992a
2 changed files with 95 additions and 15 deletions

View File

@@ -89,7 +89,9 @@ class DeletionPlanService
}
/**
* @since 3.3.0
* @param array $aClasses
* @param int $iUnixTimeLimit : max execution time in seconds since Epoch before stopping deletion. by default: no limit (ie remove all without stop)
*
* @return array<\Combodo\iTop\DataFeatureRemoval\Entity\DeletionPlanSummaryEntity>
* @throws \Combodo\iTop\DataFeatureRemoval\Helper\DataFeatureRemovalException
@@ -97,7 +99,7 @@ class DeletionPlanService
* @throws \CoreUnexpectedValue
* @throws \MySQLException
*/
public function ExecuteDeletionPlan(array $aClasses): array
public function ExecuteDeletionPlan(array $aClasses, int $iUnixTimeLimit = 0): array
{
$oDeletionPlan = $this->GetDeletionPlan($aClasses);
@@ -110,6 +112,11 @@ class DeletionPlanService
$oDeletionPlanSummaryEntity = $aSummary[$sClass] ?? new DeletionPlanSummaryEntity($sClass);
foreach ($aToUpdate as $aData) {
if ($this->IsTimeLimitExceeded($iUnixTimeLimit)) {
$aSummary[$sClass] = $oDeletionPlanSummaryEntity;
return $aSummary;
}
$oToUpdate = $aData['to_reset'];
/** @var \DBObject $oToUpdate */
foreach ($aData['attributes'] as $sRemoteExtKey => $aRemoteAttDef) {
@@ -126,20 +133,33 @@ class DeletionPlanService
$oDeletionPlanSummaryEntity = $aSummary[$sClass] ?? new DeletionPlanSummaryEntity($sClass);
foreach ($aDeletes as $sId => $aDelete) {
// Delete any existing change tracking about the current object
$oFilter = new DBObjectSearch('CMDBChangeOp');
$oFilter->AddCondition('objclass', $sClass, '=');
$oFilter->AddCondition('objkey', $sId, '=');
MetaModel::PurgeData($oFilter);
// Delete the entry
$aClassesToRemove = array_merge(MetaModel::EnumChildClasses($sClass, ENUM_CHILD_CLASSES_ALL), MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_EXCLUDELEAF, false));
foreach ($aClassesToRemove as $sParentClass) {
$oFilter = DBObjectSearch::FromOQL_AllData("SELECT $sParentClass WHERE id=:id");
$sQuery = $oFilter->MakeDeleteQuery(['id' => $sId]);
CMDBSource::DeleteFrom($sQuery);
if ($this->IsTimeLimitExceeded($iUnixTimeLimit)) {
$aSummary[$sClass] = $oDeletionPlanSummaryEntity;
return $aSummary;
}
try {
CMDBSource::Query('START TRANSACTION');
// Delete any existing change tracking about the current object
$oFilter = new DBObjectSearch('CMDBChangeOp');
$oFilter->AddCondition('objclass', $sClass, '=');
$oFilter->AddCondition('objkey', $sId, '=');
MetaModel::PurgeData($oFilter);
// Delete the entry
$aClassesToRemove = array_merge(MetaModel::EnumChildClasses($sClass, ENUM_CHILD_CLASSES_ALL), MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_EXCLUDELEAF, false));
foreach ($aClassesToRemove as $sParentClass) {
$oFilter = DBObjectSearch::FromOQL_AllData("SELECT $sParentClass WHERE id=:id");
$sQuery = $oFilter->MakeDeleteQuery(['id' => $sId]);
CMDBSource::DeleteFrom($sQuery);
}
CMDBSource::Query('COMMIT');
} catch (\Exception $e) {
\IssueLog::Exception(__METHOD__.": Cleanup failed", $e);
CMDBSource::Query('ROLLBACK');
throw $e;
}
$oDeletionPlanSummaryEntity->iDeleteCount++;
}
@@ -171,4 +191,13 @@ class DeletionPlanService
return $oDeletionPlan;
}
public function IsTimeLimitExceeded(int $iUnixTimeLimit): bool
{
if ($iUnixTimeLimit === 0) {
return false;
}
return (time() <= $iUnixTimeLimit);
}
}

View File

@@ -1,8 +1,59 @@
<?php
namespace Combodo\iTop\DataFeatureRemoval\Service\Test;
use PHPUnit\Framework\TestCase;
use Combodo\iTop\DataFeatureRemoval\Entity\DeletionPlanSummaryEntity;
use Combodo\iTop\DataFeatureRemoval\Service\DeletionPlanService;
use Combodo\iTop\Test\UnitTest\ItopCustomDatamodelTestCase;
class DeletionPlanServiceTest extends TestCase {
class DeletionPlanServiceTest extends ItopCustomDatamodelTestCase
{
public function testExecuteDeletionPlan_DeleteAllWithoutLimit()
{
$iDFRToRemoveLeaf = $this->GivenObjectInDB('DFRToRemoveLeaf', ['name' => 'ga']);
$this->GivenObjectInDB('DFRToUpdate', ['name' => 'bu', 'dfrtoremove_id' => $iDFRToRemoveLeaf]);
$iDFRRemovedCollateral = $this->GivenObjectInDB('DFRRemovedCollateral', ['name' => 'zo', 'dfrtoremove_id' => $iDFRToRemoveLeaf]);
$this->GivenObjectInDB('DFRRemovedCollateralCascade', ['name' => 'meu', 'dfrremovedcollateral_id' => $iDFRRemovedCollateral]);
$aClasses = [ 'DFRToRemoveLeaf' ];
$aRes = DeletionPlanService::GetInstance()->ExecuteDeletionPlan($aClasses);
$aExpected = [
['DFRToUpdate', 1, 0 ],
['DFRToRemoveLeaf', 0, 1 ],
['DFRRemovedCollateral', 0, 1 ],
['DFRRemovedCollateralCascade', 0, 1 ],
];
$this->AssertSummaryEquals($aExpected, $aRes, 'DeleteAllWithoutLimit');
}
private function AssertSummaryEquals(array $expected, $actual, $sMessage = '')
{
$aExpected = [];
foreach ($expected as $line) {
$sClass = $line[0];
$iUpdate = $line[1];
$iDelete = $line[2];
$oDeletionPlanSummaryEntity = new DeletionPlanSummaryEntity($sClass);
$oDeletionPlanSummaryEntity->iUpdateCount = $iUpdate;
$oDeletionPlanSummaryEntity->iDeleteCount = $iDelete;
$aExpected[$sClass] = $oDeletionPlanSummaryEntity;
}
$this->assertEquals($aExpected, $actual, $sMessage);
}
public function testExecuteDeletionPlan_StopInUpdates()
{
self::markTestSkipped();
}
public function testExecuteDeletionPlan_StopInDeletes()
{
self::markTestSkipped();
}
public function GetDatamodelDeltaAbsPath(): string
{
return __DIR__.'/deletionplan_delta.xml';
}
}