diff --git a/appinfo/info.xml b/appinfo/info.xml index 6df3cfd..c496150 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -6,7 +6,7 @@ NextMagentaCloud business functions for customer tariff evaluation This app contains business logic to evaluate provisoning info paased in as set of openid claim-like attributes - 1.0.0-nmc + 1.1.0 agpl Bernd Rederlechner NextMagentaCloudProvisioning diff --git a/lib/Db/UserQueries.php b/lib/Db/UserQueries.php index e709c27..9b0cf66 100644 --- a/lib/Db/UserQueries.php +++ b/lib/Db/UserQueries.php @@ -45,14 +45,14 @@ public function __construct(IDBConnection $db) { */ public function findDeletions(\DateTime $refDate, $limit = null, $offset = null): array { $refTs = $refDate->getTimestamp(); - + $qb = $this->db->getQueryBuilder(); - //->andWhere($qb->expr()->lt('configvalue', $qb->createNamedParameter($refTs, IQueryBuilder::PARAM_INT), IQueryBuilder::PARAM_INT)) $qb->select('userid') ->from('preferences') ->where($qb->expr()->eq('appid', $qb->createNamedParameter(Application::APP_ID))) ->andWhere($qb->expr()->eq('configkey', $qb->createNamedParameter('deletion'))) ->andWhere($qb->expr()->lt('configvalue', $qb->createNamedParameter($refTs, IQueryBuilder::PARAM_INT))) + ->orderBy('configvalue', 'ASC') // oldest first ->setMaxResults($limit) ->setFirstResult($offset); diff --git a/lib/User/UserAccountDeletionJob.php b/lib/User/UserAccountDeletionJob.php index e7af604..6829df9 100644 --- a/lib/User/UserAccountDeletionJob.php +++ b/lib/User/UserAccountDeletionJob.php @@ -5,78 +5,99 @@ use OCA\NextMagentaCloudProvisioning\Db\UserQueries; use OCP\AppFramework\Utility\ITimeFactory; use OCP\BackgroundJob\TimedJob; -use OCP\IConfig; - use OCP\ILogger; - -//use OCP\BackgroundJob\QueuedJob; use OCP\IUserManager; +use Psr\Log\LoggerInterface; -//class SlupCircuitBootJob extends QueuedJob { class UserAccountDeletionJob extends TimedJob { public const CIRCUIT_BOOT_DELAY = 300; - /** @var ILogger */ + /** @var LoggerInterface */ private $logger; - /** @var IConfig */ - private $config; - /** @var UserQueries */ private $userQueries; /** @var IUserManager */ private $userManager; - public function __construct(ITimeFactory $timeFactory, - ILogger $logger, - IConfig $config, + LoggerInterface $logger, UserQueries $userQueries, IUserManager $userManager) { parent::__construct($timeFactory); - $this->logger = $logger; // this is inconsistent with TimedJob - $this->config = $config; + $this->logger = $logger; $this->userQueries = $userQueries; $this->userManager = $userManager; - - //$destTimeString = $this->config->getAppValue('nmcprovisioning', 'deletionjobtime', '04:00:00'); - //$destTime = new \DateTime($destTimeString); - - //$diff = $destTime->getTimestamp() - $this->getLastRun(); - // negative diff means that the lastRun date lies before the plan date today, so run today - // otherwise tomorrow - $this->setInterval(3 * 60 * 60); } - // Method re-declared public for unittest purpose - public function getInterval() : int { + public function getInterval(): int { return $this->interval; } public function run($arguments) { $this->logger->info("User account deletion job started"); - - // TODO: chunk deletion loop with offset, limit - // if the set of deletion users is too big - $refTime = new \DateTime(); // NOW - $expiredUids = $this->userQueries->findDeletions($refTime); - $this->logger->info(\count($expiredUids) . " withdrawn user with expired retention period."); - foreach ($expiredUids as $uid) { - try { - $user = $this->userManager->get($uid); - $this->logger->info("Deleting " . $uid); - $user->delete(); - $this->logger->info(\count($uid) . " deleted"); - } catch (\Throwable $e) { - $this->logger->logException($e, [ - 'message' => $uid . ': Deletion failed with ' . $e->getMessage(), - 'level' => ILogger::ERROR, - 'app' => 'nmcprovisioning' - ]); + + $startTime = time(); // start time of job + $maxExecutionTime = 10800; // max. job time (3 hours) + $maxDeletionTimePerUser = 1800; // max. time per user (30 minutes) + + $refTime = new \DateTime(); // find deletions older than current time + $limit = 10; // number of users per batch + $offset = 0; // start offset + + while (time() - $startTime < $maxExecutionTime) { + $expiredUids = $this->userQueries->findDeletions($refTime, $limit, $offset); + + if (empty($expiredUids)) { + $this->logger->info("No more users to delete, exiting job."); + break; // No more users to delete } + + $this->logger->info(\count($expiredUids) . " users found for deletion in this batch."); + + foreach ($expiredUids as $uid) { + // cancel if the runtime has exceeded 3 hours + if (time() - $startTime > $maxExecutionTime) { + $this->logger->info("User account deletion job stopped after 3 hours."); + return; + } + + try { + $user = $this->userManager->get($uid); + if (!$user) { + $this->logger->warning("User $uid not found, skipping."); + continue; + } + + $this->logger->info("Deleting " . $uid); + $startDeletionTime = time(); // start time for this user + + // delete user + $user->delete(); + + // if deletion takes longer than 30 minutes, cancel + + if (time() - $startDeletionTime > $maxDeletionTimePerUser) { + $this->logger->warning("User $uid deletion took too long, skipping."); + continue; + } + + $this->logger->info("User $uid deleted successfully."); + + } catch (\Throwable $e) { + $this->logger->logException($e, [ + 'message' => "Deletion failed for $uid: " . $e->getMessage(), + 'level' => ILogger::ERROR, + 'app' => 'nmcprovisioning' + ]); + continue; // jump to the next user in case of errors + } + } + + $offset += $limit; // jump to next batch } - + $this->logger->info("User account deletion job ended"); } } diff --git a/tests/unit/UserAccounctDeletionJobTest.php b/tests/unit/UserAccounctDeletionJobTest.php index ddc6fab..8f8a646 100644 --- a/tests/unit/UserAccounctDeletionJobTest.php +++ b/tests/unit/UserAccounctDeletionJobTest.php @@ -8,14 +8,11 @@ use OCA\NextMagentaCloudProvisioning\Db\UserQueries; use OCA\NextMagentaCloudProvisioning\User\UserAccountDeletionJob; use OCP\AppFramework\Utility\ITimeFactory; - use OCP\IConfig; - -use OCP\ILogger; use OCP\IUser; - use OCP\IUserManager; use PHPUnit\Framework\TestCase; +use Psr\Log\LoggerInterface; class UserAccountDeletionJobTest extends TestCase { public function setUp(): void { @@ -25,7 +22,7 @@ public function setUp(): void { $this->userQueries = $this->createMock(UserQueries::class); $this->userManager = $this->getMockForAbstractClass(IUserManager::class); $this->job = new UserAccountDeletionJob($this->app->getContainer()->get(ITimeFactory::class), - $this->app->getContainer()->get(ILogger::class), + $this->app->getContainer()->get(LoggerInterface::class), $this->config, $this->userQueries, $this->userManager); @@ -51,7 +48,7 @@ public function setUp(): void { // ->with($this->equalTo('nmcprovisioning'), $this->equalTo('deletionjobtime')) // ->willReturn('05:00:00'); - // $refTime = new \DateTime("11:23:33"); + // $refTime = new \DateTime("11:23:33");Mura // $interval = $this->job->computeDestinationInterval($refTime); // $this->assertEquals(27 + 36*60 + 17*3600 , $interval);