function validateParams($sbParams)
 {
     //
     // VALIDATE POSSIBLE PARAMETERS FOR THIS BACKUP
     //
     // Pick a rotation method (required)
     // rotate_method - DAY_OF_WEEK  or  AFTER_SNAPSHOT_COUNT
     // Validate Rotation Method
     scheduledBackup::validateRotateMethod($sbParams['rotate_method']);
     switch ($sbParams['rotate_method']) {
         //
         // DAY_OF_WEEK options
         //
         case 'DAY_OF_WEEK':
             // rotate_day_of_week -  Create a new snapshot group on this day of the week.
             // The above uses the same day numbering as cron - 0-6 where Sunday = 0, Monday = 1, ...., Saturday = 6.
             // Multiple values accepted in comma separated format - eg. 0,3 for Sunday and Wednesday
             // Validate rotate_day_of_week
             scheduledBackup::validateRotateDayOfWeek($sbParams['rotate_day_of_week']);
             // max_snapshots_per_group - The max tatal num of snapshots allowed in a group.
             // Only used with DAY_OF_WEEK rotate_method
             // We won't take any more incremental snapshots if we already have a total of this many snapshots for the group.
             // This prevents us just continuing to endlessly take incremental snapshots if the cron schedule is never running
             // the backup on the necessary day of the week for any reason.
             // Validate max_snapshots_per_group
             scheduledBackup::validateMaxSnapshotsPerGroup($sbParams['max_snapshots_per_group']);
             // backup_skip_fatal  - If no snapshot is taken because we hit max_snapshots_per_group, but it is not the rotation day of week
             // consider it a fatal error. 0 for OFF or 1 for ON (default). This can happen if your backup never ran on the day it should have.
             // Validate backup_skip_fatal
             // if not set, assume 1 / Yes.
             if (!isset($sbParams['backup_skip_fatal'])) {
                 $sbParams['backup_skip_fatal'] = 1;
             }
             scheduledBackup::validateBackupSkipFatal($sbParams['backup_skip_fatal']);
             break;
             //
             // AFTER_SNAPSHOT_COUNT options
             //
         //
         // AFTER_SNAPSHOT_COUNT options
         //
         case 'AFTER_SNAPSHOT_COUNT':
             // rotate_snapshot_no - We rotate on the snapshot after this many in a group.
             // Eg. if 7 and daily snapshots, you are creating a new group for the 8th.
             // Validate rotate_snapshot_no
             scheduledBackup::validateRotateSnapshotNo($sbParams['rotate_snapshot_no']);
             break;
         default:
             throw new Exception('rotatingBackupTaker->validateParams: ' . "Error: Could not find a method to validate this backups parameters. This should never happen.");
     }
     // GLOBAL rotate_method options...
     // max_snapshot_groups - The maximum number of groups of snapshots we will maintain.
     // If 2, we would throw away group 1 after successfully taking the SEED for the third group.
     // validate max_snapshot_groups
     // must be set..
     scheduledBackup::validateMaxSnapshotGroups($sbParams['max_snapshot_groups']);
     // validate maintain_materialized_copy (if set)
     if (isset($sbParams['maintain_materialized_copy'])) {
         scheduledBackup::validateMaintainMaterializedCopy($sbParams['maintain_materialized_copy']);
     }
     //
     // If we made it this far, the backup parameters should be valid... proceed with the actual backup!
     //
     return true;
 }
 function getNew($hostname, $name, $strategyCode, $cronExpression, $volumeName, $datadir, $mysqlUser, $mysqlPass)
 {
     $datadir = rtrim($datadir, '/');
     // Validate inputs
     host::validateHostname($hostname);
     scheduledBackup::validateName($name);
     backupStrategy::validateStrategyCode($strategyCode);
     scheduledBackup::validateCronExpression($cronExpression);
     backupVolume::validateName($volumeName);
     scheduledBackup::validateDatadirPath($datadir);
     scheduledBackup::validateMysqlUser($mysqlUser);
     scheduledBackup::validateMysqlPass($mysqlPass);
     // Lookup host by name
     $hostGetter = new hostGetter();
     $hostGetter->setLogStream($this->log);
     if (!($host = $hostGetter->getByName($hostname))) {
         throw new ProcessingException("Error: No Host could be found with a hostname matching: {$hostname}");
     }
     // Lookup volume by name
     $volumeGetter = new volumeGetter();
     $volumeGetter->setLogStream($this->log);
     if (!($volume = $volumeGetter->getByName($volumeName))) {
         throw new ProcessingException("Error: No Volume could be found with a name matching: {$volumeName}");
     }
     // Lookup backup strategy by code
     $strategyGetter = new backupStrategyGetter();
     $strategyGetter->setLogStream($this->log);
     if (!($strategy = $strategyGetter->getByCode($strategyCode))) {
         throw new ProcessingException("Error: No Backup Strategy could be found matching the code: {$strategyCode}");
     }
     // Check for existing scheduled backups with the same name for this host
     if ($existing = $this->getByHostnameAndName($hostname, $name)) {
         throw new ProcessingException("Error: A Scheduled Backup already exists for host {$hostname} with a name matching: {$name}");
     }
     // INSERT the row
     $conn = dbConnection::getInstance($this->log);
     // Create a new scheduledBackup entry
     // For now we just always create with "xtrabackup" binary used (mysql_type_id = 1)..
     // Maybe this can change later..
     $sql = "INSERT INTO scheduled_backups \n\t\t\t\t\t( name, cron_expression, datadir_path, mysql_user, mysql_password, \n\t\t\t\t\t  host_id, backup_volume_id, mysql_type_id, backup_strategy_id \n\t\t\t\t\t) VALUES ( \n\t\t\t\t\t\t'" . $conn->real_escape_string($name) . "',\n\t\t\t\t\t\t'" . $conn->real_escape_string($cronExpression) . "',\n\t\t\t\t\t\t'" . $conn->real_escape_string($datadir) . "',\n\t\t\t\t\t\t'" . $conn->real_escape_string($mysqlUser) . "',\n\t\t\t\t\t\t'" . $conn->real_escape_string($mysqlPass) . "',\n\t\t\t\t\t\t" . $host->id . ",\n\t\t\t\t\t\t" . $volume->id . ",\n\t\t\t\t\t\t1,\n\t\t\t\t\t\t" . $strategy->id . "\n\t\t\t\t\t)";
     if (!$conn->query($sql)) {
         throw new DBException('scheduledBackupGetter->getNew: ' . "Error: Query: {$sql} \nFailed with MySQL Error: {$conn->error}");
     }
     // Init the object
     $scheduledBackup = new scheduledBackup($conn->insert_id);
     $scheduledBackup->setLogStream($this->log);
     // Init the default scheduledBackup parameters for the strategy
     $sql = "INSERT INTO scheduled_backup_params (scheduled_backup_id, backup_strategy_param_id, param_value) \n\t\t\t\t\t\tSELECT " . $scheduledBackup->id . ", backup_strategy_param_id, default_value \n\t\t\t\t\t\tFROM backup_strategy_params\n\t\t\t\t\t\tWHERE backup_strategy_id=" . $strategy->id;
     if (!$conn->query($sql)) {
         throw new DBException('scheduledBackupGetter->getNew: ' . "Error: Query: {$sql} \nFailed with MySQL Error: {$conn->error}");
     }
     return $scheduledBackup;
 }
 function validateParams($params)
 {
     // max_snapshots
     scheduledBackup::validateMaxSnapshots($params['max_snapshots']);
     return true;
 }
 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;
 }