From 028c0384a2553c40de679c13e88feb6d1dda95b9 Mon Sep 17 00:00:00 2001 From: Huong Nguyen Date: Wed, 27 Dec 2023 16:14:07 +0700 Subject: [PATCH] MDL-69615 core_backup: Send backup report email once the tasks are done --- .../util/helper/backup_cron_helper.class.php | 27 +++- lang/en/admin.php | 1 + .../task/automated_backup_report_task.php | 86 ++++++++++ lib/classes/task/task_trait.php | 21 ++- lib/db/tasks.php | 9 ++ lib/tests/task/automated_backup_task_test.php | 150 ++++++++++++++++++ version.php | 2 +- 7 files changed, 289 insertions(+), 7 deletions(-) create mode 100644 lib/classes/task/automated_backup_report_task.php create mode 100644 lib/tests/task/automated_backup_task_test.php diff --git a/backup/util/helper/backup_cron_helper.class.php b/backup/util/helper/backup_cron_helper.class.php index d8e581ce041..ca030d8311b 100644 --- a/backup/util/helper/backup_cron_helper.class.php +++ b/backup/util/helper/backup_cron_helper.class.php @@ -137,9 +137,11 @@ abstract class backup_cron_automated_helper { $rs->close(); // Send email to admin if necessary. - if ($emailpending) { - self::send_backup_status_to_admin($admin); - } + set_config( + 'backup_auto_emailpending', + $emailpending ? 1 : 0, + 'backup', + ); } finally { // Everything is finished release lock. $lock->release(); @@ -188,7 +190,7 @@ abstract class backup_cron_automated_helper { * @param stdClass $admin * @return array */ - private static function send_backup_status_to_admin($admin) { + public static function send_backup_status_to_admin($admin) { global $DB, $CFG; mtrace("Sending email to admin"); @@ -386,7 +388,22 @@ abstract class backup_cron_automated_helper { 'courseid' => $backupcourse->courseid, 'adminid' => $admin->id )); - \core\task\manager::queue_adhoc_task($asynctask); + $taskid = \core\task\manager::queue_adhoc_task($asynctask); + + // Get the queued tasks. + $queuedtasks = []; + if ($value = get_config('backup', 'backup_auto_adhoctasks')) { + $queuedtasks = explode(',', $value); + } + if ($taskid) { + $queuedtasks[] = (int) $taskid; + } + // Save the queued tasks. + set_config( + 'backup_auto_adhoctasks', + implode(',', $queuedtasks), + 'backup', + ); $backupcourse->laststatus = self::BACKUP_STATUS_QUEUED; $DB->update_record('backup_courses', $backupcourse); diff --git a/lang/en/admin.php b/lang/en/admin.php index 4e639a7c8f9..779257a9ded 100644 --- a/lang/en/admin.php +++ b/lang/en/admin.php @@ -1397,6 +1397,7 @@ $string['tasklockcleanuptask'] = 'Clean up ad hoc task metadata'; $string['taskadmintitle'] = 'Tasks'; $string['taskanalyticscleanup'] = 'Analytics cleanup'; $string['taskautomatedbackup'] = 'Automated backups'; +$string['taskautomatedbackup_report'] = 'Automated backups report'; $string['taskbackupcleanup'] = 'Clean backup tables, logs and files'; $string['taskbadgescron'] = 'Award badges'; $string['taskbadgesmessagecron'] = 'Background processing for sending badges notifications'; diff --git a/lib/classes/task/automated_backup_report_task.php b/lib/classes/task/automated_backup_report_task.php new file mode 100644 index 00000000000..e88d29c9e58 --- /dev/null +++ b/lib/classes/task/automated_backup_report_task.php @@ -0,0 +1,86 @@ +. + +namespace core\task; + +/** + * Report task for core automation backup. + * + * @package core + * @copyright 2024 Huong Nguyen + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class automated_backup_report_task extends scheduled_task { + + /** + * Get a descriptive name for the task (shown to admins). + * + * @return string + */ + public function get_name(): string { + return get_string('taskautomatedbackup_report', 'admin'); + } + + /** + * Do the job. + */ + public function execute(): void { + global $DB, $CFG; + $queuedtasks = []; + if ($value = get_config('backup', 'backup_auto_adhoctasks')) { + $queuedtasks = explode(',', $value); + } + if (!empty($queuedtasks)) { + // Some automated backup tasks are still running. + // Check the status for each task. + foreach ($queuedtasks as $taskid) { + if (!$DB->record_exists('task_adhoc', ['id' => $taskid])) { + // The task has been completed. Remove it from the queue. + if (($key = array_search($taskid, $queuedtasks)) !== false) { + unset($queuedtasks[$key]); + } + } + } + // Update the queue. + set_config( + 'backup_auto_adhoctasks', + implode(',', $queuedtasks), + 'backup', + ); + } + if (empty($queuedtasks) && get_config('backup', 'backup_auto_emailpending')) { + // All the automated backup tasks have been completed. Send the report. + $admin = get_admin(); + if (!$admin) { + mtrace("Error: No admin account was found"); + return; + } + // Send email to admin if necessary. + require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php'); + require_once($CFG->dirroot . '/backup/util/helper/backup_cron_helper.class.php'); + \backup_cron_automated_helper::send_backup_status_to_admin($admin); + // Remove the configs. + unset_config( + 'backup_auto_adhoctasks', + 'backup', + ); + unset_config( + 'backup_auto_emailpending', + 'backup', + ); + } + } +} diff --git a/lib/classes/task/task_trait.php b/lib/classes/task/task_trait.php index 323c2dd6901..28763605f5d 100644 --- a/lib/classes/task/task_trait.php +++ b/lib/classes/task/task_trait.php @@ -32,9 +32,28 @@ trait task_trait { */ protected function execute_task(string $taskclass): void { // Run the scheduled task. - ob_start(); + $this->start_output_buffering(); $task = manager::get_scheduled_task($taskclass); $task->execute(); + $this->stop_output_buffering(); + } + + /** + * Helper to start output buffering. + */ + protected function start_output_buffering(): void { + ob_start(); + } + + /** + * Helper to stop output buffering. + * + * @return string|null The output buffer contents or null if output buffering is not active. + */ + protected function stop_output_buffering(): ?string { + $output = ob_get_contents(); ob_end_clean(); + + return $output; } } diff --git a/lib/db/tasks.php b/lib/db/tasks.php index 8cff4bbb671..905c73f06e3 100644 --- a/lib/db/tasks.php +++ b/lib/db/tasks.php @@ -457,4 +457,13 @@ $tasks = array( 'dayofweek' => 'R', 'month' => '*', ], + [ + 'classname' => 'core\task\automated_backup_report_task', + 'blocking' => 0, + 'minute' => '*', + 'hour' => '*', + 'day' => '*', + 'month' => '*', + 'dayofweek' => '*', + ], ); diff --git a/lib/tests/task/automated_backup_task_test.php b/lib/tests/task/automated_backup_task_test.php new file mode 100644 index 00000000000..acef35a825b --- /dev/null +++ b/lib/tests/task/automated_backup_task_test.php @@ -0,0 +1,150 @@ +. + +namespace core\task; + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once($CFG->dirroot . '/backup/util/helper/backup_cron_helper.class.php'); + +/** + * Class containing unit tests for the task do the automation backup and report. + * + * @package core + * @copyright 2024 Huong Nguyen + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class automated_backup_task_test extends \advanced_testcase { + + use task_trait; + + /** + * Test the automated backup and report tasks. + * + * @covers \automated_backup_report_task::execute + * @covers \backup_cron_automated_helper::send_backup_status_to_admin + * @covers \backup_cron_automated_helper::run_automated_backup + * @covers \backup_cron_automated_helper::check_and_push_automated_backups + */ + public function test_automated_backup(): void { + global $DB; + $this->resetAfterTest(); + + // Enable automated back up. + set_config( + 'backup_auto_active', + true, + 'backup', + ); + set_config( + 'backup_auto_weekdays', + '1111111', + 'backup', + ); + + // Create courses. + $course1 = $this->getDataGenerator()->create_course(); + $course2 = $this->getDataGenerator()->create_course(); + + // Create course backups. + $DB->insert_records( + 'backup_courses', + [ + [ + 'courseid' => $course1->id, + 'laststatus' => \backup_cron_automated_helper::BACKUP_STATUS_NOTYETRUN, + 'nextstarttime' => time() - 10, + ], + [ + 'courseid' => $course2->id, + 'laststatus' => \backup_cron_automated_helper::BACKUP_STATUS_NOTYETRUN, + 'nextstarttime' => time() - 10, + ], + ], + ); + + // Verify that we don't have any running backup tasks. + $this->assertEmpty(get_config('backup', 'backup_auto_adhoctasks')); + $this->assertEmpty(get_config('backup', 'backup_auto_emailpending')); + + // Redirect messages to sink. + $sink = $this->redirectMessages(); + // Trigger the automated backup scheduled task. + $this->execute_task('\core\task\automated_backup_task'); + $messages = $sink->get_messages(); + $sink->close(); + + // Scheduled task should not send report yet, because there are still running backup tasks. + $this->assertCount(0, $messages); + + // Check that the backup tasks have been created. + $this->assertTrue($DB->record_exists('backup_courses', ['courseid' => $course1->id])); + $this->assertTrue($DB->record_exists('backup_courses', ['courseid' => $course2->id])); + $this->assertNotEmpty(get_config('backup', 'backup_auto_adhoctasks')); + $this->assertEquals(1, get_config('backup', 'backup_auto_emailpending')); + + // Redirect messages to sink and stop buffer output from CLI task. + $sink = $this->redirectMessages(); + // Trigger the automated backup report scheduled task. + $this->execute_task('\core\task\automated_backup_report_task'); + $messages = $sink->get_messages(); + $sink->close(); + + // Scheduled task should not send report yet, because there are still running backup tasks. + $this->assertCount(0, $messages); + + // Execute only one ad-hoc backup task. + $value = get_config('backup', 'backup_auto_adhoctasks'); + $queuedtasks = explode(',', $value); + $task = manager::get_adhoc_task($queuedtasks[0]); + $this->start_output_buffering(); + $task->execute(); + $this->stop_output_buffering(); + manager::adhoc_task_complete($task); + + // Redirect messages to sink and stop buffer output from CLI task. + $sink = $this->redirectMessages(); + // Trigger the automated backup report scheduled task. + $this->execute_task('\core\task\automated_backup_report_task'); + $messages = $sink->get_messages(); + $sink->close(); + + // Scheduled task should not send report yet, because there are still running backup tasks. + $this->assertCount(0, $messages); + + // Execute the remaining ad-hoc backup task. + $this->start_output_buffering(); + $this->runAdhocTasks('\core\task\course_backup_task'); + $this->stop_output_buffering(); + + // Redirect messages to sink. + $sink = $this->redirectMessages(); + // Trigger the automated backup report scheduled task. + $this->execute_task('\core\task\automated_backup_report_task'); + $messages = $sink->get_messages(); + $sink->close(); + + // Verify that all the backup tasks have been completed and all the configs have been cleared. + $this->assertEmpty(get_config('backup', 'backup_auto_adhoctasks')); + $this->assertEmpty(get_config('backup', 'backup_auto_emailpending')); + // Verify that the report has been sent. + $this->assertCount(1, $messages); + $message = reset($messages); + $this->assertEquals(get_admin()->id, $message->useridto); + $this->assertEquals('backup', $message->eventtype); + } +} diff --git a/version.php b/version.php index 9a9972baff2..9d1a9236875 100644 --- a/version.php +++ b/version.php @@ -29,7 +29,7 @@ defined('MOODLE_INTERNAL') || die(); -$version = 2024011200.00; // YYYYMMDD = weekly release date of this DEV branch. +$version = 2024011200.01; // YYYYMMDD = weekly release date of this DEV branch. // RR = release increments - 00 in DEV branches. // .XX = incremental changes. $release = '4.4dev (Build: 20240112)'; // Human-friendly version name