Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion appinfo/info.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<summary>NextMagentaCloud business functions for customer tariff evaluation</summary>
<description>This app contains business logic to evaluate provisoning info paased in
as set of openid claim-like attributes</description>
<version>1.0.0-nmc</version>
<version>1.1.0</version>
<licence>agpl</licence>
<author>Bernd Rederlechner</author>
<namespace>NextMagentaCloudProvisioning</namespace>
Expand Down
4 changes: 2 additions & 2 deletions lib/Db/UserQueries.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
107 changes: 64 additions & 43 deletions lib/User/UserAccountDeletionJob.php
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
}
9 changes: 3 additions & 6 deletions tests/unit/UserAccounctDeletionJobTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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);
Expand All @@ -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);

Expand Down