Commit 5273402e authored by Adrian Rossouw's avatar Adrian Rossouw Committed by adrian

Added restore functionality for #237555. Implement rollback as well, if...

Added restore functionality for #237555. Implement rollback as well, if something goes wrong it rolls back to the initial system status
parent 11d12e65
<?php
/**
* @file mysql db api extension
*
* A collection of helper functions used by the main provision hooks to accomplish their tasks.
*/
/**
* Generate a new mysql database and user account for the specified credentials
*/
function _provision_mysql_new_site_db($db_name, $db_user, $db_passwd, $db_host) {
provision_set_active_db(_provision_master_db_url());
_provision_mysql_create_database($db_name);
if ( !_provision_mysql_database_exists($db_name) ) {
provision_set_error(PROVISION_DB_ERROR);
provision_log("error", "Database could not be created.");
provision_set_active_db();
return FALSE;
}
_provision_mysql_grant($db_name, $db_user, $db_passwd);
_provision_mysql_grant($db_name, $db_user, $db_passwd, $db_host);
if ($data['site_mysql_old_passwords']) {
_provision_mysql_old_password($db_user, $db_passwd);
_provision_mysql_old_password($db_user, $db_passwd, $db_host);
}
_provision_mysql_flush();
provision_set_active_db();
#TODO : Test to confirm that the database is actually writeable. Taking this on faith for now.
}
/**
* Remove the database and user account for the supplied credentials
*/
function _provision_mysql_destroy_site_db($db_name, $db_user, $db_passwd, $db_host) {
provision_set_active_db(_provision_master_db_url());
if ( _provision_mysql_database_exists($data['site_db_name']) ) {
provision_log("notice", t("Dropping database @dbname", array('@dbname' => $db_name)));
_provision_mysql_drop_database($db_name);
}
if ( _provision_mysql_database_exists($db_name) ) {
provision_set_error(PROVISION_DB_ERROR);
provision_log("error", "Database could not be destroyed.");
provision_set_active_db();
return FALSE;
}
provision_log("notice", "Revoking privileges");
_provision_mysql_revoke($db_name, $db_user);
_provision_mysql_revoke($db_name, $db_user, $db_host);
_provision_mysql_flush();
provision_set_active_db();
}
function _provision_mysql_database_exists($name) {
return db_result(db_query("SHOW DATABASES LIKE '%s'", $name));
}
function _provision_mysql_drop_database($name) {
db_query("DROP DATABASE `%s`", $name);
}
function _provision_mysql_create_database($name) {
db_query("CREATE DATABASE %s", $name);
}
function _provision_mysql_can_create_database() {
$test = 'provision_test';
_provision_mysql_create_database($test);
if (_provision_mysql_database_exists($test)) {
_provision_mysql_drop_database($test);
return true;
}
return false;
}
function _provision_mysql_grant($name, $username, $password, $host = '') {
$host = ($host) ? $host : '%';
db_query("GRANT ALL PRIVILEGES ON %s.* TO %s@`%s` IDENTIFIED BY '%s'", $name, $username, $host, $password);
}
function _provision_mysql_revoke($name, $username, $host = '') {
$host = ($host) ? $host : '%';
db_query("REVOKE ALL PRIVILEGES ON %s.* FROM %s@`%s`", $name, $username, $host);
}
function _provision_mysql_old_password($username, $password, $host = '') {
$host = ($host) ? $host : '%';
db_query("SET PASSWORD FOR '%s'@'%s' = OLD_PASSWORD('%s')", $username, $host, $password);
}
function _provision_mysql_flush() {
db_query("FLUSH PRIVILEGES");
}
function _provision_master_db_url($data = array()) {
return sprintf("%s://%s:%s@%s/mysql", PROVISION_DB_TYPE, PROVISION_DB_USER, PROVISION_DB_PASSWD, PROVISION_DB_HOST);
}
function _provision_mysql_import_dump($dump_file, $db_name, $db_user, $db_passwd, $db_host) {
$exists = provision_path("exists", $dump_file, true,
t('Found database dump at @path.'),
t('No database dump was found at @path.'),
FILE_FRAMEWORK_ERROR);
if ($exists) {
$readable = provision_path("readable", $dump_file, true, '',
t('The database dump at @path could not be read.'),
PROVISION_PERM_ERROR);
if ($readable) {
provision_shell_exec("mysql -u%s -p%s -h%s %s < %s",
$db_user, $db_passwd, $db_host, $db_name, $dump_file );
}
}
}
/**
* Find a viable database name, based on available information.
*
* This function exists solely to work past mysql's database name restrictions.
* As mysql also does not have the ability to rename databases, it is completely
* possible that sites will be running with derivative names on the same server,
* until the upgrade / restore process is completed.
*/
function _provision_mysql_suggest_db_name($url, $data) {
provision_set_active_db(_provision_master_db_url());
if ($data['site_id']) {
$id_suggest = 'site_' . $data['site_id'];
$suggest[] = $id_suggest;
for ($i = 0; $i < 100; $i++) {
$suggest[] = $id_suggest . '_' . $i;
}
}
else {
// This is a last option, and not ideal
// Provision only users will trigger this mostly.
$url_suggest = substr(0, 14, str_replace(array(".", "-"), '' , ereg_replace("^www\.", "", $url)));
$suggest[] = $url_suggest;
for ($i = 0; $i < 100; $i++) {
$suggest[] = $url_suggest . '_' . $i;
}
}
foreach ($suggest as $option) {
if (!_provision_mysql_database_exists($option)) {
provision_set_active_db();
return $option;
}
}
}
......@@ -8,7 +8,7 @@
*/
include_once('provision_mysql.inc');
include_once('provision.mysql.inc');
/**
* @ingroup provisionui
......@@ -106,10 +106,18 @@ function provision_mysql_provision_pre_install($url, &$data) {
$data['site_db_user'] = $data['site_db_name'];
return _provision_mysql_new_site_db($data['site_db_name'], $data['site_db_user'], $data['site_db_passwd'], $data['site_db_host']);
}
function provision_mysql_provision_pre_install_rollback($url, &$data) {
_provision_mysql_destroy_site_db($data['site_db_name'], $data['db_user'], $data['db_passwd'], $data['db_host']);
unset($data['site_db_host']);
unset($data['site_db_type']);
unset($data['site_db_passwd']);
unset($data['site_db_user']);
unset($data['site_db_name']);
}
function provision_mysql_provision_pre_restore($url, $backup_file, &$data) {
function provision_mysql_provision_pre_restore($url, &$data) {
// store a backup of the credentials of the site.
$keys = array('site_db_name', 'site_db_passwd', 'site_db_user', 'site_db_host');
foreach ($keys as $key) {
......@@ -122,15 +130,18 @@ function provision_mysql_provision_pre_restore($url, $backup_file, &$data) {
$data['site_db_user'] = $data['site_db_name'];
$success = _provision_mysql_new_site_db($data['site_db_name'], $data['site_db_user'], $data['site_db_passwd'], $data['site_db_host']);
if (!$success) {
// store a backup of the credentials of the site.
foreach ($keys as $key) {
$data[$key] = $data['old_' . $key];
}
return false; # i really really need to dust up on my rollback code.
if ($success) {
_provision_mysql_import_dump($data['restore_file'], $data['site_db_name'], $data['site_db_user'], $data['site_db_passwd'], $data['site_db_host']);
}
_provision_mysql_import_dump($url, $data);
}
function provision_mysql_provision_pre_restore_rollback($url, &$data) {
_provision_mysql_destroy_site_db($data['site_db_name'], $data['db_user'], $data['db_passwd'], $data['db_host']);
//Restore the original database credentials of the site.
foreach ($keys as $key) {
$data[$key] = $data['old_' . $key];
}
}
/**
......@@ -142,8 +153,10 @@ function provision_mysql_provision_delete($url, &$data) {
return _provision_mysql_destroy_site_db($data['site_db_name'], $data['db_user'], $data['db_passwd'], $data['db_host']);
}
// Rollback doesn't apply here yet. Unless we trigger a restore of the first dump
// made. Which could go on infinitely if something is really long.
function provision_mysql_provision_post_restore($url, &$data) {
return _provision_mysql_destroy_site_db($data['old_site_db_name'], $data['old_db_user'], $data['old_db_passwd'], $data['old_db_host']);
return _provision_mysql_destroy_site_db($data['old_site_db_name'], $data['old_site_db_user'], $data['old_site_db_passwd'], $data['old_site_db_host']);
}
......@@ -155,6 +168,8 @@ function provision_mysql_provision_backup($url, &$data) {
/**
* Implementation of hook_provision_verify
*
* Can't be rolled back.
*/
function provision_mysql_provision_verify() {
provision_set_active_db(_provision_master_db_url());
......
......@@ -343,7 +343,8 @@ function provision_drupal_provision_verify($url, &$data) {
}
/**
* Remove any directories for the site in sites/
* Remove any directories for the site in sites
* This can't be rolled back. so won't even try.
*/
function provision_drupal_provision_delete($url, $data) {
return _provision_recursive_delete("sites/$url");
......@@ -359,11 +360,6 @@ function provision_drupal_find_sites() {
return $sites;
}
function provision_drupal_provision_pre_restore($url, $backup_file, &$data) {
}
function _provision_drupal_get_cvs_versions($files) {
foreach ($files as $modulename => $file) {
$project = array();
......
......@@ -23,13 +23,13 @@
* disable - Disable an installed Drupal site. Changes the virtual host config file so that it redirects to provision_disabled_site_redirect_url
* enable - Re-enable a site that has already been disabled. Recreates the virtual host file.
* delete - Generates a back up of the site, and then removes all references to it.
* restore - Revert to a previous backup of the site.
*
* Not implemented yet :
* upgrade - Accepts a site package (backup) as argument, and redeploys it, running the upgrade processes on it.
* Uses hook_provision_pre_upgrade(), hook_provision_upgrade() and hook_provision_post_upgrade() hooks,
* and allows clean roll back if any errors occur. Will include stringent checking of module versions,
* and allow unit tests to be run.
* restore - Revert to a previous backup of the site.
* rename - Change the url of a site. This requires moving of files, and numerous other issues.
*/
......@@ -47,6 +47,7 @@
* program, but are configurable.
*/
function provision_init() {
// Set up defines for platform
if (function_exists('drush_get_option')) {
$docroot = drush_get_option(array("r", "root"), $_SERVER['PWD']);
......@@ -54,17 +55,19 @@ function provision_init() {
else {
$docroot = $_SERVER['pwd'];
}
$path = ($docroot) ? $docroot : $_SERVER['DOCUMENT_ROOT'];
define('PROVISION_DOCROOT_PATH', rtrim($path, '/'));
define('PROVISION_SITES_PATH', rtrim($path, '/'));
define('PROVISION_SITES_PATH', rtrim($path, '/') . '/sites');
$parts = explode("/", rtrim($path, '/'));
array_pop($parts);
define('PROVISION_PARENT_PATH', rtrim(implode("/" , $parts), '/'));
define('PROVISION_BACKUP_PATH', variable_get('provision_backup_path', PROVISION_PARENT_PATH . '/backups'));
define('PROVISION_CONFIG_PATH', variable_get('provision_config_path', PROVISION_PARENT_PATH . '/config')) ;
define('PROVISION_BACKUP_PATH',
variable_get('provision_backup_path', PROVISION_PARENT_PATH . '/backups'));
define('PROVISION_CONFIG_PATH',
variable_get('provision_config_path', PROVISION_PARENT_PATH . '/config')) ;
define('PROVISION_VHOST_PATH', PROVISION_CONFIG_PATH . '/vhost.d');
define('PROVISION_DRUSHRC_PATH', PROVISION_CONFIG_PATH . '/drushrc.d');
......@@ -78,6 +81,13 @@ function provision_init() {
define('PROVISION_DB_PASSWD', variable_get('provision_db_passwd', 'root'));
define('PROVISION_DB_HOST', variable_get('provision_db_host', 'localhost'));
global $base_url;
define('PROVISION_WEB_DISABLE_URL',
variable_get('provision_web_disable_url', $base_url . '/provision/disable'));
define('PROVISION_WEB_MAINTENENCE_URL',
variable_get('provision_web_maintenance_url', $base_url . '/provision/maintenance'));
}
......@@ -587,35 +597,68 @@ function _provision_verify($url = '') {
* and leave the current site directory and database in the right place, and remove all cruft that
* was created by this process.
*/
function _provision_restore($site, $backup_file) {
if (!_provision_drupal_site_installed($url)) {
function _provision_restore($url, $restore_file) {
if (!($exists = _provision_drupal_site_installed($url))) {
// this can probably be done more consistently with another
// provision_path like function.
provision_log("Error", "Site has not been installed yet.");
provision_set_error(PROVISION_SITE_NOT_FOUND);
provision_output($url, $data);
}
$exists = !provision_path("exists", $backup_file, true,
$exists &= provision_path("exists", $restore_file, true,
t("Restoring site from @path"),
t("Could not find backup file @path"),
PROVISION_FRAMEWORK_ERROR);
if (!$exists) {
provision_output($url, $data);
if ($exists) {
$data = provision_get_site_data($url);
$data['restore_file'] = $restore_file;
$phases = array("pre_restore", "restore", "post_restore");
$completed = array();
$rolled_back = false; // initializes to false.
foreach ($phases as $phase) {
$rolled_back = provision_invoke($phase, $url, $data, $rolled_back);
if (!$rolled_back) {
$completed[] = $phase;
}
else {
break; // exit out of the loop, to allow any changes to be reversed next.
}
}
if ($rolled_back) {
// An error has occurred, and we must undo all the changes we made, in reverse
// This works by triggering the _rollback functions for each of the hooks.
foreach (array_reverse($completed) as $phase) {
provision_invoke($phase, $url, $data, $rolled_back);
}
}
else {
provision_save_site_data($url, $data);
}
}
provision_output($url, $data);
}
$data = provision_get_site_data($url);
function provision_provision_pre_restore($url, &$data) {
_provision_backup_site($url, $data); # Backup site for posterity, before rolling back.
$rolled_back = provision_invoke("pre_restore", $url, $data); // if we need to make the site unavailable etc.
provision_shell_exec("tar -zxf %s -C %s/%s.restore", $backup_file, PROVISION_SITES_PATH, $url); # check out old directory.
$rolled_back = provision_invoke("restore", $url, $data); // do the actual database/file manipulation
_provision_recursive_delete(PROVISION_BACKUP_PATH . '/' . $url . '.tmp');
provision_path("extract", $data['restore_file'], PROVISION_SITES_PATH . "/$url.restore",
t('Successfully extracted the contents of @path to @confirm'),
t('Failed to extract the contents of @path to @confirm'),
PROVISION_PERM_ERROR, PROVISION_FRAMEWORK_ERROR);
}
$rolled_back = provision_invoke("post_restore", $url, $data); // do the actual database/file manipulation
function provision_provision_pre_restore_rollback($url, $data) {
_provision_recursive_delete(PROVISION_SITES_PATH . "/$url.restore");
}
$data['site_installed'] = TRUE;
provision_save_site_data($url, $data);
function provision_provision_post_restore($url, $data) {
_provision_recursive_delete(PROVISION_SITES_PATH . "/$url.restore");
}
function _provision_disable($url) {
......@@ -634,7 +677,6 @@ function _provision_disable($url) {
provision_output($url, $data);
}
function _provision_enable($url) {
if (!_provision_drupal_site_installed($url)) {
provision_log("Error", "Site has not been installed yet.");
......
......@@ -213,6 +213,27 @@ function provision_path_switch_paths($path1, &$path2, &$reason) {
return $path1;
}
function provision_path_extract($path, &$target, &$reason) {
if (file_exists($path) && is_readable($path)) {
if (is_writeable(dirname($target)) && !file_exists($target) && !is_dir($target)) {
mkdir($target);
provision_shell_exec("tar -zxf %s -C %s", $path, $target);
$target = TRUE;
return TRUE;
}
else {
$reason = t("The target directory could not be written to");
return false;
}
}
else {
$reason = t("Backup file could not be opened");
return false;
}
}
/**
*@} end filegroup
*/
......@@ -199,8 +199,7 @@ function provision_apache_provision_enable($url, &$data) {
* Implementation of hook_provision_disable
*/
function provision_apache_provision_disable($url, &$data) {
global $base_url; # XXX: hack, i don't know where this comes from, but it's the url I want (hostmaster.koumbit.net)
$data['redirect_url'] = variable_get('provision_apache_disabled_url', $base_url . '/provision/disabled');
$data['redirect_url'] = PROVISION_WEB_DISABLE_URL;
_provision_apache_create_vhost_config($url, $data, _provision_apache_redirect_template());
_provision_apache_restart_apache();
}
......@@ -287,13 +286,37 @@ function provision_apache_provision_verify() {
* Implementation of hook_provision_pre_restore
*/
function provision_apache_provision_pre_restore($url, &$data) {
global $base_url; # XXX: hack, i don't know where this comes from, but it's the url I want (hostmaster.koumbit.net)
$data['redirect_url'] = variable_get('provision_apache_maintenance_url', $base_url . '/provision/maintenance');
$data['redirect_url'] = PROVISION_WEB_MAINTENANCE_URL;
_provision_apache_create_vhost_config($url, $data, _provision_apache_redirect_template());
_provision_apache_restart_apache();
}
function provision_apache_provision_pre_restore_rollback($url, $data) {
_provision_apache_create_vhost_config($url, $data);
_provision_apache_restart_apache();
_provision_recursive_delete(PROVISION_SITES_PATH . "/$url.restore");
}
function provision_apache_provision_restore($url, $data) {
$old = PROVISION_SITES_PATH . "/$url.restore";
$new = PROVISION_SITES_PATH . "/$url";
provision_path("switch_paths", $old, $new ,
t('Swapping out the @path and @confirm directories was successful.'),
t('Swapping the @path and @confirm directories has failed.'),
PROVISION_PERM_ERROR);
}
// Luckily this is reversable =)
function provision_apache_provision_restore_rollback($url, $data) {
provision_apache_provision_restore($url, $data);
}
function provision_apache_provision_post_restore($url, &$data) {
_provision_apache_create_vhost_config($url, $data);
_provision_apache_restart_apache();
}
// The old db details should have been switched back by the mysql rollback at this point.
function provision_apache_provision_post_restore_rollback($url, &$data) {
provision_apache_provision_post_restore($url, $data);
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment