function restoreLocal($backupSnapshot, $path) { $this->validate($backupSnapshot); // Sanitise the path if ($path == '/' || strlen($path) == 0) { throw new Exception('backupRestorer->restoreLocal: ' . "Error: Detected unsafe path to restore to - {$path}"); } // Now check that it exists - if not try create it if (!file_exists($path)) { if (!mkdir($path)) { throw new Exception('backupRestorer->restoreLocal: ' . "Error: Attempted to create directory for restore, but failed -- {$path}"); } // Set permissions to be rwx owner and rx group. if (!@chmod($path, 0750)) { throw new Exception('backupRestorer->restoreLocal: ' . "Error: Attempted to change permissions for newly created restore path, but failed -- {$path}"); } } else { if (!is_dir($path)) { throw new Exception('backupRestorer->restoreLocal: ' . "Error: Specified path location is not a directory -- {$path}"); } } if (!is_writeable($path)) { throw new Exception('backupRestorer->restoreLocal: ' . "Error: The specified path location is no writeable -- {$path}"); } // Strip trailing space (and spaces if there are any for some weird reason $path = rtrim($path, '/ '); switch (get_class($backupSnapshot)) { // Handle a regular backupSnapshot class case 'backupSnapshot': // Get the group for the backup and find the seed for that group. $group = $backupSnapshot->getSnapshotGroup(); $scheduledBackup = $backupSnapshot->getScheduledBackup(); // Find the seed of the backupSnapshot $seedSnapshot = $group->getSeed(); // Copy the seed to the right place $seedPath = $seedSnapshot->getPath(); if ($seedSnapshot->id == $backupSnapshot->id) { $msg = 'Copying snapshot from ' . $seedPath . ' to path ' . $path; } else { $msg = 'Copying seed snapshot from ' . $seedPath . ' to path ' . $path; } $this->infolog->write($msg, XBM_LOG_INFO); $copyCommand = 'cp -R ' . $seedPath . '/* ' . $path . '/'; exec($copyCommand, $output, $returnVar); if ($returnVar != 0) { throw new Exception('backupRestorer->restoreLocal: ' . "Error: Failed to copy from {$seedPath} to path {$path} using command: {$copyCommand} -- Got output:\n" . implode("\n", $output)); } $this->infolog->write('Done copying.', XBM_LOG_INFO); // If the seed is what we wanted to restore, we're done! if ($seedSnapshot->id == $backupSnapshot->id) { return true; } $xbBinary = $scheduledBackup->getXtraBackupBinary(); // Proceed with applying any incrementals needed to get to the snapshot we want! $snapshotMerger = new backupSnapshotMerger(); $parentSnapshot = $seedSnapshot; do { $deltaSnapshot = $parentSnapshot->getChild(); $deltaPath = $deltaSnapshot->getPath(); $this->infolog->write('Merging incremental snapshot deltas from ' . $deltaPath . ' into path ' . $path, XBM_LOG_INFO); try { $snapshotMerger->mergePaths($path, $deltaPath, $xbBinary); } catch (MergeException $mergeEx) { throw new MergeException($mergeEx->getMessage(), $mergeEx->getErrorMessage(), $deltaSnapshot); } $this->infolog->write('Done merging.', XBM_LOG_INFO); $parentSnapshot = $deltaSnapshot; } while ($deltaSnapshot->id != $backupSnapshot->id); break; case 'materializedSnapshot': $snapPath = $backupSnapshot->getPath(); $msg = 'Copying snapshot from ' . $snapPath . ' to path ' . $path; $this->infolog->write($msg, XBM_LOG_INFO); $copyCommand = 'cp -R ' . $snapPath . '/* ' . $path . '/'; exec($copyCommand, $output, $returnVar); if ($returnVar != 0) { throw new Exception('backupRestorer->restoreLocal: ' . "Error: Failed to copy from {$snapPath} to path {$path} using command: {$copyCommand} -- Got output:\n" . implode("\n", $output)); } $this->infolog->write('Done copying.', XBM_LOG_INFO); break; default: throw new ProcessingException('backupRestorer->restoreLocal: ' . "Error: Unrecognized class of backup snapshot to restore."); break; } $this->infolog->write('Local restore to ' . $path . ' complete!', XBM_LOG_INFO); return true; }
function materializeLatest($scheduledBackup = false) { global $config; // Validate if ($scheduledBackup === false || !is_object($scheduledBackup)) { throw new Exception('materializedSnapshotManager->materializeLatest: ' . "Error: Expected a scheduledBackup object to be passed as a parameter, but did not get one."); } // Find the latest backup snapshot for the scheduledBackup $snapshotGroups = $scheduledBackup->getSnapshotGroupsNewestToOldest(); if (sizeOf($snapshotGroups) == 0) { throw new Exception('materializedSnapshotManager->materializeLatest: ' . "Error: Expected to find at least one snapshot group for the scheduledBackup, but got none."); } // Get the latest snapshot and its info.. $latestSnapshot = $snapshotGroups[0]->getMostRecentCompletedBackupSnapshot(); $latestSnapInfo = $latestSnapshot->getInfo(); $latestSnapPath = $latestSnapshot->getPath(); // Attempt to get the materialized snapshot for the scheduled Backup into "prevMaterialized" $prevMaterialized = $scheduledBackup->getMostRecentCompletedMaterializedSnapshot(); // if we DO get one then we need to create a new materializedSnapshot if ($prevMaterialized != false) { // Set state to UPDATING $prevMaterialized->setStatus('UPDATING'); // Get the backup snapshot that the prevMaterialized is linked to $prevSnapshot = $prevMaterialized->getBackupSnapshot(); // and its info.. $prevSnapInfo = $prevSnapshot->getInfo(); $prevSnapPath = $prevSnapshot->getPath(); // if the materialized is already the latest if ($latestSnapshot->id == $prevSnapshot->id) { // Just exit - nothing to do. return true; } // otherwise proceed - initialize a new materializedSnapshot $newMaterialized = new materializedSnapshot(); $newMaterialized->setLogStream($this->log); try { $newMaterialized->init($scheduledBackup, $latestSnapshot); $materialPath = $newMaterialized->getPath(); // Is the group for the materializedSnapshot the same as the latest snapshot? if ($latestSnapInfo['snapshot_group_num'] == $prevSnapInfo['snapshot_group_num']) { // if yes - is the backupSnapshot the prevMaterialized is linked to a SEED? if ($prevSnapInfo['type'] == 'SEED') { // if yes then -- // we need to copy that seed to the new path first $copyCommand = 'cp -R ' . $prevSnapPath . '/* ' . $materialPath . '/'; exec($copyCommand, $output, $returnVar); if ($returnVar != 0) { throw new Exception('materializedSnapshotManager->materializeLatest: ' . "Error: Failed to copy from {$prevSnapPath} to path {$materialPath} using command: {$copyCommand} -- Got output:\n" . implode("\n", $output)); } } else { // If the prevSnapshot is not a SEED then we need to move it to $materialPath if (!rename($prevMaterialized->getPath(), $materialPath)) { throw new Exception('materializedSnapshotManager->materializeLatest: ' . "Error: Failed to move {$prevSnapPath} to {$materialPath}."); } } // now just roll forward, applying deltas step by step until we reach the current backup $xbBinary = $scheduledBackup->getXtraBackupBinary(); $snapshotMerger = new backupSnapshotMerger(); $parentSnapshot = $prevSnapshot; do { $deltaSnapshot = $parentSnapshot->getChild(); $deltaPath = $deltaSnapshot->getPath(); $this->infolog->write('Merging incremental snapshot deltas from ' . $deltaPath . ' into path ' . $materialPath, XBM_LOG_INFO); try { $snapshotMerger->mergePaths($materialPath, $deltaPath, $xbBinary); } catch (MergeException $mergeEx) { // This snapshot merge failed during materialize // We should remove the bad snapshot and it's children // Get the group for the delta that just failed $group = $deltaSnapshot->getSnapshotGroup(); // Get the snaps in the group from newest to oldest $snapshotsForGroup = $group->getAllSnapshotsNewestToOldest(); // Iterate over them newest to oldest, destroying them, until we meet the one that failed foreach ($snapshotsForGroup as $snap) { $snap->destroy(); if ($snap->id == $deltaSnapshot->id) { break; } } throw new Exception('materializedSnapshotManager->materializeLatest: ' . "Error: An error occurred while trying to materialze the latest snapshot. A set of deltas" . " could not be merged successfully.\nThe offending incremental backup and any other backup snapshots that depend upon it have been deleted.\n" . $mergeEx->getErrorMessage()); } $this->infolog->write('Done merging.', XBM_LOG_INFO); $parentSnapshot = $deltaSnapshot; } while ($deltaSnapshot->id != $latestSnapshot->id); } else { // if groups for snapshot differ, is the backupSnapshot that we need to update to a SEED? if ($latestSnapInfo['type'] == 'SEED') { // if yes, just create the symlink $newMaterialized->symlinkToSnapshot($latestSnapshot); } else { // if no, copy the SEED for the snapshotGroup of the new snapshot to the new path, then apply deltas $backupRestorer = new backupRestorer(); $backupRestorer->setInfoLogStream($this->infolog); $backupRestorer->setLogStream($this->log); try { $backupRestorer->restoreLocal($latestSnap, $materialPath); } catch (MergeException $mergeEx) { // This snapshot merge failed during materialize // We should remove the bad snapshot and it's children $failSnap = $mergeEx->getFailedSnapshot(); // Get the group for the delta that just failed $group = $failSnap->getSnapshotGroup(); // Get the snaps in the group from newest to oldest $snapshotsForGroup = $group->getAllSnapshotsNewestToOldest(); // Iterate over them newest to oldest, destroying them, until we meet the one that failed foreach ($snapshotsForGroup as $snap) { $snap->destroy(); if ($snap->id == $failSnap->id) { break; } } throw new Exception('materializedSnapshotManager->materializeLatest: ' . "Error: An error occurred while trying to materialze the latest snapshot. A set of deltas" . " could not be merged successfully.\nThe offending incremental backup and any other backup snapshots that depend upon it have been deleted.\n" . $mergeEx->getErrorMessage()); } } } // destroy the old snapshot $prevMaterialized->destroy(); } catch (Exception $e) { $prevMaterialized->destroy(); $newMaterialized->setStatus('FAILED'); if ($config['SYSTEM']['cleanup_on_failure'] == true) { $newMaterialized->deleteFiles(); } throw $e; } } else { // If no previous materialized snapshot exists at all.. // Initialize a new materialized snapshot $newMaterialized = new materializedSnapshot(); $newMaterialized->setLogStream($this->log); try { $newMaterialized->init($scheduledBackup, $latestSnapshot); $materialPath = $newMaterialized->getPath(); // if we do not get one then check if the snapshot is a SEED if ($latestSnapInfo['type'] == 'SEED') { // if yes - just create a symlink to it $newMaterialized->symlinkToSnapshot($latestSnapshot); } else { // if no - use the regular restore function to restore it to the target path $backupRestorer = new backupRestorer(); $backupRestorer->setInfoLogStream($this->infolog); $backupRestorer->setLogStream($this->log); try { $backupRestorer->restoreLocal($latestSnapshot, $materialPath); } catch (MergeException $mergeEx) { // This snapshot merge failed during materialize // We should remove the bad snapshot and it's children $failSnap = $mergeEx->getFailedSnapshot(); // Get the group for the delta that just failed $group = $failSnap->getSnapshotGroup(); // Get the snaps in the group from newest to oldest $snapshotsForGroup = $group->getAllSnapshotsNewestToOldest(); // Iterate over them newest to oldest, destroying them, until we meet the one that failed foreach ($snapshotsForGroup as $snap) { $snap->destroy(); if ($snap->id == $failSnap->id) { break; } } throw new Exception('materializedSnapshotManager->materializeLatest: ' . "Error: An error occurred while trying to materialze the latest snapshot. A set of deltas" . " could not be merged successfully.\nThe offending incremental backup and any other backup snapshots that depend upon it have been deleted.\n" . $mergeEx->getErrorMessage()); } } } catch (Exception $e) { $newMaterialized->setStatus('FAILED'); if ($config['SYSTEM']['cleanup_on_failure'] == true) { $newMaterialized->deleteFiles(); } throw $e; } } $newMaterialized->setStatus('COMPLETED'); }
function applyRetentionPolicy(backupJob $job) { global $config; $scheduledBackup = $job->getScheduledBackup(); $this->infolog->write("Checking to see if any snapshots need to be merged into the seed backup.", XBM_LOG_INFO); if (!is_object($scheduledBackup)) { throw new Exception('continuousIncrementalBackupTaker->applyRetentionPolicy: ' . "Error: This function requires a scheduledBackup object as a parameter."); } $conn = dbConnection::getInstance($this->log); $sql = "SELECT backup_snapshot_id FROM backup_snapshots WHERE status='COMPLETED' AND scheduled_backup_id=" . $scheduledBackup->id . " AND snapshot_time IS NOT NULL ORDER BY snapshot_time ASC"; if (!($res = $conn->query($sql))) { throw new Exception('continuousIncrementalBackupTaker->applyRetentionPolicy: ' . "Error: Query: {$sql} \nFailed with MySQL Error: {$conn->error}"); } // Get info for this scheduledBackup $info = $scheduledBackup->getInfo(); // Get the params/options for this scheduledBackup $params = $scheduledBackup->getParameters(); // Validate them $this->validateParams($params); // Build service objects for later use $snapshotGetter = new backupSnapshotGetter(); $snapshotMerger = new backupSnapshotMerger(); // Check to see if the number of rows we have is more than the number of snapshots we should have at a max while ($res->num_rows > $params['max_snapshots']) { // Grab the first row - it is the SEED if (!($row = $res->fetch_array())) { throw new Exception('continuousIncrementalBackupTaker->applyRetentionPolicy: ' . "Error: Could not retrieve the object ID for the seed of Scheduled Backup ID " . $scheduledBackup->id); } $seedSnapshot = $snapshotGetter->getById($row['backup_snapshot_id']); // Grab the second row - it is the DELTA to be collapsed. if (!($row = $res->fetch_array())) { throw new Exception('continuousIncrementalBackupTaker->applyRetentionPolicy: ' . "Error: Could not retrieve the object ID for the seed of Scheduled Backup ID " . $scheduledBackup->id); } $deltaSnapshot = $snapshotGetter->getById($row['backup_snapshot_id']); $this->infolog->write("Merging deltas in Backup Snapshot ID #" . $deltaSnapshot->id . " with Backup Snapshot ID #" . $seedSnapshot->id . ".", XBM_LOG_INFO); // Merge them together $snapshotMerger->mergeSnapshots($seedSnapshot, $deltaSnapshot); // Check to see what merge work is needed now. $sql = "SELECT backup_snapshot_id FROM backup_snapshots WHERE status='COMPLETED' AND scheduled_backup_id=" . $scheduledBackup->id . " AND snapshot_time IS NOT NULL ORDER BY snapshot_time ASC"; if (!($res = $conn->query($sql))) { throw new Exception('continuousIncrementalBackupTaker->applyRetentionPolicy: ' . "Error: Query: {$sql} \nFailed with MySQL Error: {$conn->error}"); } } return true; }