function getRunningBackups() { global $config; if (!is_numeric($this->id)) { throw new Exception('host->getRunningBackups: ' . "Error: The ID for this object is not an integer."); } $backupGetter = new runningBackupGetter(); $backupGetter->setLogStream($this->log); return $backupGetter->getByHost($this); }
function upgrade() { // Look for running backups without checking schema version $runningBackupGetter = new runningBackupGetter(); $runningBackupGetter->setSchemaVersionChecks(false); // Get the runningbackups - this getter automatically removes stale entries, so it should only return truly running pids... $runningBackups = $runningBackupGetter->getAll(); $backupCount = sizeOf($runningBackups); // If we find running backups, abort with error if ($backupCount > 0) { throw new ProcessingException('schemaUpgrader->upgrade: ' . "Error: Detected " . $backupCount . " backup(s) currently running. Please retry upgrading the schema later."); } // Create a new DB connection getter that does not check the schema version... $conn = dbConnection::getInstance($this->log, false); $schemaVersion = $conn->getSchemaVersion(); switch (true) { // If the schema versions match - all is good - nothing to do case $schemaVersion == XBM_SCHEMA_VERSION: $this->resultMsg = "The schema version of the XtraBackup Manager database is already at the expected version number (" . XBM_SCHEMA_VERSION . ")."; return true; break; // If the schema version of the DB is higher than what we need - throw an error! // If the schema version of the DB is higher than what we need - throw an error! case $schemaVersion > XBM_SCHEMA_VERSION: $this->resultMsg = "The schema version of the XtraBackup Manager database (" . $schemaVersion . ") is higher than the expected version number (" . XBM_SCHEMA_VERSION . ")."; return false; break; // Schema version is < expected version - we need to upgrade! // Schema version is < expected version - we need to upgrade! case $schemaVersion < XBM_SCHEMA_VERSION: // Find all the files in the $XBM_AUTO_INSTALLDIR/sql/changes/ directory global $XBM_AUTO_INSTALLDIR; $files = glob($XBM_AUTO_INSTALLDIR . '/sql/changes/*'); // Sort them just in case the OS gives them back in a strange order asort($files); // Walk the array and build a list of scripts to run of files that are numeric and > $schemaVersion $toRun = array(); foreach ($files as $filename) { $basename = basename($filename); if (is_numeric($basename) && $basename > $schemaVersion && $basename <= XBM_SCHEMA_VERSION) { $toRun[] = $filename; } } // Walk over the array applying each schema update foreach ($toRun as $scriptName) { $version = basename($scriptName); // Run the script $sql = file_get_contents($scriptName); if (!($res = $conn->query($sql))) { throw new Exception('schemaUpgrader->upgrade: ' . "Error: Query: {$sql} \nFailed with MySQL Error: {$conn->error}"); } // Update the schemaVersion in the DB $sql = "UPDATE schema_version SET version=" . $version; if (!($res = $conn->query($sql))) { throw new Exception('schemaUpgrader->upgrade: ' . "Error: Query: {$sql} \nFailed with MySQL Error: {$conn->error}"); } } $this->resultMsg = "The XtraBackup Manager database schema was successfully upgraded to schema version " . XBM_SCHEMA_VERSION . "."; return true; break; // Catch all - should never get here // Catch all - should never get here default: $this->resultMsg = "An issue occurred when comparing schema versions. This probably indicated a bug in XtraBackup Manager."; return false; break; } // Catch all - should never get here $this->resultMsg = "An issue occurred when comparing schema versions. This probably indicated a bug in XtraBackup Manager."; return false; // Get the schema version }
function destroy() { // Validate this... if (!is_numeric($this->id)) { throw new Exception('scheduledBackup->destroy: ' . "Error: The ID for this object is not an integer."); } $queueManager = new queueManager(); $queueManager->setLogStream($this->log); // We need to take over all queues for this backup and make sure nothing is running. $queues = array('scheduledBackup:' . $this->id, 'retentionApply:' . $this->id, 'postProcess:' . $this->id); foreach ($queues as $queue) { $ticket = $queueManager->getTicketNumber($queue); if (!$queueManager->checkFrontOfQueue($queue, $ticket)) { throw new ProcessingException("Error: Cannot remove the Scheduled Backup Task as it is currently running."); } } // Check to see if anything is running for this scheduledBackup $runningBackupGetter = new runningBackupGetter(); $runningBackupGetter->setLogStream($this->log); $runningBackups = $runningBackupGetter->getByScheduledBackup($this); if (sizeOf($runningBackups) > 0) { throw new ProcessingException("Error: Cannot remove the Scheduled Backup Task as it is currently running."); } // Get all snapshots and destroy them.. $groups = $this->getSnapshotGroupsNewestToOldest(); foreach ($groups as $group) { $snapshots = $group->getAllSnapshotsNewestToOldest(); foreach ($snapshots as $snapshot) { $snapshot->destroy(); } } // If we have a materialized snapshot - destroy that too if ($latestMaterialized = $this->getMostRecentCompletedMaterializedSnapshot()) { $latestMaterialized->destroy(); } $conn = dbConnection::getInstance($this->log); // Remove DB the entries for this scheduledBackup $sql = "DELETE sb.*, sbp.* FROM scheduled_backups sb JOIN scheduled_backup_params sbp USING (scheduled_backup_id) WHERE scheduled_backup_id = " . $this->id; if (!$conn->query($sql)) { throw new DBException('scheduledBackup->setParam: ' . "Error: Query {$sql} \nFailed with MySQL Error: {$conn->error}"); } return; }
function takeScheduledBackupSnapshot(scheduledBackup $scheduledBackup) { global $config; $this->launchTime = time(); // Get the backup strategy for the scheduledBackup $sbInfo = $scheduledBackup->getInfo(); // Get the host of the backup $sbHost = $scheduledBackup->getHost(); // Get host info $hostInfo = $sbHost->getInfo(); // create a backupTakerFactory $takerFactory = new backupTakerFactory(); // use it to get the right object type for the backup strategy $backupTaker = $takerFactory->getBackupTakerByStrategy($sbInfo['strategy_code']); // Setup to write to host log $infolog = new logStream($config['LOGS']['logdir'] . '/hosts/' . $hostInfo['hostname'] . '.log', $this->infologVerbose, $config['LOGS']['level']); $this->setInfoLogStream($infolog); try { $msg = 'Initializing Scheduled Backup "' . $sbInfo['name'] . '" (ID #' . $sbInfo['scheduled_backup_id'] . ') for host: ' . $hostInfo['hostname'] . ' ... '; $this->infolog->write($msg, XBM_LOG_INFO); $msg = 'Using Backup Strategy: ' . $sbInfo['strategy_name']; $this->infolog->write($msg, XBM_LOG_INFO); // Create an entry for this backup job $jobGetter = new backupJobGetter(); $jobGetter->setLogStream($this->log); $job = $jobGetter->getNew($scheduledBackup); // Check to see if we can even start this party // First, take a number in the queues for the scheduledBackup and the host itself.. $queueManager = new queueManager(); $queueManager->setLogStream($this->log); // Set the global queueName for use later. $globalQueueName = 'scheduledBackup:GLOBAL'; // Take a number in the scheduledBackup queue for THIS backup... $schedQueueName = 'scheduledBackup:' . $sbInfo['scheduled_backup_id']; $schedQueueTicket = $queueManager->getTicketNumber($schedQueueName); // Take a number in the host queue... $hostQueueName = 'hostBackup:' . $sbInfo['host_id']; $hostQueueTicket = $queueManager->getTicketNumber($hostQueueName); // If we are not at the front of the scheduledBackup queue when we start up, then just exit // assume another job is running already for this scheduledBackup.. we dont queue up dupe backup jobs // we skip them if ($queueManager->checkFrontOfQueue($schedQueueName, $schedQueueTicket) == false) { // Release our tickets in the queues, then throw exception... $queueManager->releaseTicket($hostQueueTicket); $queueManager->releaseTicket($schedQueueTicket); $this->infolog->write("Detected this scheduled backup job is already running, exiting...", XBM_LOG_ERROR); throw new Exception('backupSnapshotTaker->takeScheduledBackupSnapshot: ' . "Error: Detected this scheduled backup job is already running."); } // Create this object now, so we don't recreate it a tonne of times in the loop $runningBackupGetter = new runningBackupGetter(); // Mark us as "QUEUED" while we figure out if we can launch... $job->setStatus('Queued'); $readyToRun = false; while ($readyToRun == false) { // If we are not at the front of the queue for this host, then sleep/wait until we are. if (!$queueManager->checkFrontOfQueue($hostQueueName, $hostQueueTicket)) { $this->infolog->write("There are jobs before this one in the queue for this host. Sleeping " . XBM_SLEEP_SECS . " before checking again...", XBM_LOG_INFO); for ($i = 0; $i <= XBM_SLEEP_SECS; $i++) { if ($job->isKilled()) { throw new KillException('The backup was killed by an administrator.'); } sleep(1); } continue; } // We are at the front of the queue for this host... // Check to see how many backups are running for the host already... $runningBackups = $sbHost->getRunningBackups(); // If we are at or greater than max num of backups for the host, then sleep before we try again. if (sizeOf($runningBackups) >= $config['SYSTEM']['max_host_concurrent_backups']) { // Output to info log - this currently spits out every 30 secs (define is 30 at time of writing) // maybe it is too much $this->infolog->write("Found " . sizeOf($runningBackups) . " backup(s) running for this host out of a maximum of " . $config['SYSTEM']['max_host_concurrent_backups'] . " per host. Sleeping " . XBM_SLEEP_SECS . " before retry...", XBM_LOG_INFO); for ($i = 0; $i <= XBM_SLEEP_SECS; $i++) { if ($job->isKilled()) { throw new KillException('The backup was killed by an administrator.'); } sleep(1); } continue; } // Only take a ticket in the global queue if we dont already have one // Wait until this point to prevent blocking up the GLOBAL queue when the host itself is blocked.. if (!isset($globalQueueTicket)) { $globalQueueTicket = $queueManager->getTicketNumber($globalQueueName); } // If we are not at the front of the queue for global backups, then sleep/wait until we are if (!$queueManager->checkFrontOfQueue($globalQueueName, $globalQueueTicket)) { $this->infolog->write("There are jobs before this one in the global backup queue. Sleeping " . XBM_SLEEP_SECS . " before checking again...", XBM_LOG_INFO); for ($i = 0; $i <= XBM_SLEEP_SECS; $i++) { if ($job->isKilled()) { throw new KillException('The backup was killed by an administrator.'); } sleep(1); } continue; } // Now check to see the how many backups are running globally and if we should be allowed to run... $globalRunningBackups = $runningBackupGetter->getAll(); if (sizeOf($globalRunningBackups) >= $config['SYSTEM']['max_global_concurrent_backups']) { //output to info log -- currentl every 30 secs based on define at time of writing // maybe too much? $this->infolog->write("Found " . sizeOf($globalRunningBackups) . " backup(s) running out of a global maximum of " . $config['SYSTEM']['max_global_concurrent_backups'] . ". Sleeping " . XBM_SLEEP_SECS . " before retry...", XBM_LOG_INFO); for ($i = 0; $i <= XBM_SLEEP_SECS; $i++) { if ($job->isKilled()) { throw new KillException('The backup was killed by an administrator.'); } sleep(1); } continue; } // If we made it to here - we are ready to run! $readyToRun = true; } // Populate the backupTaker with the relevant settings like log/infolog/Verbose, etc. $backupTaker->setLogStream($this->log); $backupTaker->setInfoLogStream($this->infolog); $backupTaker->setInfoLogVerbose($this->infologVerbose); $backupTaker->setLaunchTime($this->launchTime); $backupTaker->setTicketsToReleaseOnStart(array($hostQueueTicket, $globalQueueTicket)); // Kick off takeScheduledBackupSnapshot method of the actual backup taker $job->setStatus('Performing Backup'); $backupTaker->takeScheduledBackupSnapshot($job); // Release the ticket for running the backup.. $queueManager->releaseTicket($schedQueueTicket); $retentionQueueName = 'retentionApply:' . $sbInfo['scheduled_backup_id']; $retentionQueueTicket = $queueManager->getTicketNumber($retentionQueueName); // Proceed once we're at the start of the retention policy queue for this scheduled backup while (!$queueManager->checkFrontOfQueue($retentionQueueName, $retentionQueueTicket)) { $this->infolog->write('There is already a task applying retention policy for this scheduled backup. Sleeping ' . XBM_SLEEP_SECS . ' before retry...', XBM_LOG_INFO); for ($i = 0; $i <= XBM_SLEEP_SECS; $i++) { if ($job->isKilled()) { throw new KillException('The backup was killed by an administrator.'); } sleep(1); } } // Apply the retention policy $this->infolog->write("Applying snapshot retention policy ...", XBM_LOG_INFO); $job->setStatus('Deleting Old Backups'); $backupTaker->applyRetentionPolicy($job); $this->infolog->write("Application of retention policy complete.", XBM_LOG_INFO); $queueManager->releaseTicket($retentionQueueTicket); // Perform any post processingA // Get ticket/queue $postProcessQueueName = 'postProcess:' . $sbInfo['scheduled_backup_id']; $postProcessQueueTicket = $queueManager->getTicketNumber($postProcessQueueName); while (!$queueManager->checkFrontOfQueue($postProcessQueueName, $postProcessQueueTicket)) { $this->infolog->write('There is already a task performing post processing for this scheduled backup. Sleeping ' . XBM_SLEEP_SECS . ' before retry...', XBM_LOG_INFO); for ($i = 0; $i <= XBM_SLEEP_SECS; $i++) { if ($job->isKilled()) { throw new KillException('The backup was killed by an administrator.'); } sleep(1); } } $this->infolog->write("Performing any post-processing necessary ...", XBM_LOG_INFO); $job->setStatus('Performing Post-Processing'); $backupTaker->postProcess($job); $this->infolog->write("Post-processing completed.", XBM_LOG_INFO); $queueManager->releaseTicket($postProcessQueueTicket); $this->infolog->write("Scheduled Backup Task Complete!", XBM_LOG_INFO); $job->setStatus('Completed'); } catch (KillException $e) { if (isset($job)) { $job->setStatus('Killed'); } $this->infolog->write('Exiting after the backup job was killed...', XBM_LOG_ERROR); throw $e; } catch (Exception $e) { if (isset($job)) { $job->setStatus('Failed'); } $this->infolog->write('An error occurred while trying to perform the backup. Proceeding to log some details to help debug...', XBM_LOG_ERROR); $this->infolog->write('Error Caught: ' . $e->getMessage(), XBM_LOG_ERROR); $this->infolog->write('Trace: ' . $e->getTraceAsString(), XBM_LOG_ERROR); throw $e; } return true; }