Skip to content

Commit

Permalink
Check MySQL permissions for backup
Browse files Browse the repository at this point in the history
* added file perm and grant exceptions to BackupDAO
* check for new exception(s) and show proper error message to user
* added mock DAOs for backup controller tests
Closes #715, closes #1101
  • Loading branch information
Mark Wilkie authored and ginatrapani committed Nov 7, 2011
1 parent 3f593bc commit ee0be8d
Show file tree
Hide file tree
Showing 12 changed files with 274 additions and 69 deletions.
30 changes: 21 additions & 9 deletions docs/source/troubleshoot/common/backupcannotwrite.rst
Original file line number Original file line Diff line number Diff line change
@@ -1,12 +1,24 @@
Can't create/write to file Cannot back up or export data
========================== =============================


While running a database backup either during the test run or using the application, you may get the error: While running a database backup or export either during the test run or using the application, you may get the error:


``PDOException: SQLSTATE[HY000]: General error: 1 Can't ``It looks like the MySQL user does not have the proper permissions to grant back up/export access``
create/write to file '<thinkup>/webapp/_lib/view/compiled_view/tu_encoded_locations.txt' (Errcode: 13)``


ThinkUp's backup tool doesn't have the permissions it needs to back up your files. Make sure that the mysql user or
has GRANT FILE ON privileges in the database as well as write privileges to the compiled_view directory. If you still
get the error, make sure that there aren't restrictive permissions set in any of the compiled_view folder's ``It looks like the MySQL user does not have the proper file permissions to back up/export data``
parent folders up the tree which would keep the mysql user from writing to that directory.
or

``PDOException: SQLSTATE[HY000]: General error: 1 Can't create/write to file
'<thinkup>/webapp/_lib/view/compiled_view/tu_encoded_locations.txt' (Errcode: 13)``

ThinkUp's backup or export tool doesn't have the permissions it needs to back up your files. Make sure that the MySQL
user has GRANT FILE and LOCK TABLE privileges in the database as well as write privileges to the compiled_view
directory.

`Find out more about MySQL GRANT permission <http://dev.mysql.com/doc/refman/5.1/en/grant.html>`_.

If you still get the error, make sure that there aren't restrictive permissions set in any of the
compiled_view folder's parent folders up the tree which would keep the MySQL user from writing to that directory.
2 changes: 2 additions & 0 deletions docs/source/troubleshoot/messages/index.rst
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -11,3 +11,5 @@ This is a working list of ThinkUp's error, information, and success messages, an
upgrading upgrading
crawlinprogress crawlinprogress
slickgridonlyshowing slickgridonlyshowing
mysqlgrant
mysqlfile
5 changes: 5 additions & 0 deletions docs/source/troubleshoot/messages/mysqlfile.rst
Original file line number Original file line Diff line number Diff line change
@@ -0,0 +1,5 @@
The MySQL user does not have the proper file permissions to backup/export data
==============================================================================

The Thinkup MySQL user does not have the proper file permissions to backup or export data. Make Sure the MySQL
server has write privileges to the ThinkUp's ``_lib/view/compiled_view/`` directory.
6 changes: 6 additions & 0 deletions docs/source/troubleshoot/messages/mysqlgrant.rst
Original file line number Original file line Diff line number Diff line change
@@ -0,0 +1,6 @@
The MySQL user does not have the proper permissions to grant backup/export access
=================================================================================

The Thinkup MySQL user needs GRANT permission to backup or export data. Make sure the MySQL user has been granted both
FILE and LOCK TABLE permissions to the ThinkUp database.
`Find out more about MySQL GRANT permission <http://dev.mysql.com/doc/refman/5.1/en/grant.html>`_.
50 changes: 48 additions & 2 deletions tests/TestOfBackupController.php
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -105,8 +105,7 @@ public function testLoadBackupViewCLIWarn() {
$old_count = UpgradeController::$WARN_TABLE_ROW_COUNT; $old_count = UpgradeController::$WARN_TABLE_ROW_COUNT;
UpgradeController::$WARN_TABLE_ROW_COUNT = 2; UpgradeController::$WARN_TABLE_ROW_COUNT = 2;
$results = $controller->control(); $results = $controller->control();
$this->assertPattern('/we recommend that you use the.*command line backup tool.*when backing up ThinkUp/sm', $this->assertPattern('/we recommend that you use the/', $results);
$results);
$table_counts = $v_mgr->getTemplateDataItem('high_table_row_count'); $table_counts = $v_mgr->getTemplateDataItem('high_table_row_count');
$this->assertNotNull($table_counts); $this->assertNotNull($table_counts);
$this->assertNotNull(3, $table_counts['count']); // tu_plugins, defaults to three $this->assertNotNull(3, $table_counts['count']); // tu_plugins, defaults to three
Expand Down Expand Up @@ -238,4 +237,51 @@ public function testRestore() {
$this->assertEqual($data['id'], 1); $this->assertEqual($data['id'], 1);
$this->assertEqual($data['name'], 'Twitter'); $this->assertEqual($data['name'], 'Twitter');
} }

public function testMySQLExportFails() {
// backup DAO mapping
$dao_mapping_backup = DAOFactory::$dao_mapping['BackupDAO'];

$this->simulateLogin('me@example.com', true);
$controller = new BackupController(true);
$_GET['backup'] = 'true';

// no grant perms
DAOFactory::$dao_mapping['BackupDAO']['mysql'] = 'TestBackupDAOGrantFail';
$results = $controller->go();
$this->assertPattern("/It looks like the MySQL user does not have the proper permissions to/", $results);

// no file perms
$controller = new BackupController(true);
DAOFactory::$dao_mapping['BackupDAO']['mysql'] = 'TestBackupDAOFileFail';
$results = $controller->go();
$this->assertPattern("/It looks like the MySQL user does not have the proper file permissions/", $results);

// restore DAO mapping
DAOFactory::$dao_mapping['BackupDAO']['mysql'] = $dao_mapping_backup;
}
}

/**
* a mock BackupDAO to test grant error
*/
class TestBackupDAOGrantFail implements BackupDAO {
public function import($zipfile) {
// does nothing
}
public function export($backup_file = null) {
throw new MySQLGrantException("Mysql does not have GRANT FILE ON permissions to write to: /bla");
}
}

/**
* a mock BackupDAO to test file error
*/
class TestBackupDAOFileFail implements BackupDAO {
public function import($zipfile) {
// does nothing
}
public function export($backup_file = null) {
throw new OpenFileException("Mysql does not have permissions to write to: /bla");
}
} }
41 changes: 41 additions & 0 deletions tests/TestOfExportServiceUserDataController.php
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
require_once dirname(__FILE__).'/init.tests.php'; require_once dirname(__FILE__).'/init.tests.php';
require_once THINKUP_ROOT_PATH.'webapp/_lib/extlib/simpletest/autorun.php'; require_once THINKUP_ROOT_PATH.'webapp/_lib/extlib/simpletest/autorun.php';
require_once THINKUP_ROOT_PATH.'webapp/config.inc.php'; require_once THINKUP_ROOT_PATH.'webapp/config.inc.php';
require_once THINKUP_ROOT_PATH.'webapp/_lib/model/class.ExportMySQLDAO.php';


class TestOfExportServiceUserDataController extends ThinkUpUnitTestCase { class TestOfExportServiceUserDataController extends ThinkUpUnitTestCase {


Expand Down Expand Up @@ -150,4 +151,44 @@ public function testExport() {
$this->assertTrue($zip_files["/favorites.tmp"]); $this->assertTrue($zip_files["/favorites.tmp"]);
$za->close(); $za->close();
} }

public function testMySQLErrors() {
$this->simulateLogin('me@example.com', true);
// backup of DAO mapping
$dao_mapping_backup = DAOFactory::$dao_mapping['ExportDAO'];

DAOFactory::$dao_mapping['ExportDAO']['mysql'] = 'TestExportDAOFileFail';
$controller = new ExportServiceUserDataController(true);
$_POST['instance_id'] = 1;
$results = $controller->go();
$this->assertPattern("/MySQL user does not have the proper file permissions/", $results);

DAOFactory::$dao_mapping['ExportDAO']['mysql'] = 'TestExportDAOGrantFail';
$controller = new ExportServiceUserDataController(true);
$_POST['instance_id'] = 1;
$results = $controller->go();
$this->assertPattern("/MySQL user does not have the proper permissions to/", $results);

DAOFactory::$dao_mapping['ExportDAO']['mysql'] = $dao_mapping_backup;
}
} }


/**
* a mock ExportDAO to test file error
*/
class TestExportDAOFileFail {

public function dropExportedPostsTable($backup_file = null) {
throw new Exception("Can't get stat of file /foo");
}
}

/**
* a mock ExportDAO to test grant error
*/
class TestExportDAOGrantFail {
public function dropExportedPostsTable($backup_file = null) {
throw new Exception("Mysql does not have GRANT FILE ON permissions to write to: /bla");
}
}
1 change: 1 addition & 0 deletions tests/TestOfUpgradeController.php
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ public function testDatabaseMigrationNeededHighTableCount() {
$this->simulateLogin('me@example.com', true); $this->simulateLogin('me@example.com', true);
$controller = new UpgradeController(true); $controller = new UpgradeController(true);
$results = $controller->go(); $results = $controller->go();

$this->assertPattern('/needs 1 database update/', $results); $this->assertPattern('/needs 1 database update/', $results);
$v_mgr = $controller->getViewManager(); $v_mgr = $controller->getViewManager();
$this->assertNull($v_mgr->getTemplateDataItem('high_table_row_count') ) ; $this->assertNull($v_mgr->getTemplateDataItem('high_table_row_count') ) ;
Expand Down
54 changes: 35 additions & 19 deletions webapp/_lib/controller/class.BackupController.php
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -53,32 +53,48 @@ public function adminControl() {
// pass the count of the table with the most records // pass the count of the table with the most records
$table_stats_dao = DAOFactory::getDAO('TableStatsDAO'); $table_stats_dao = DAOFactory::getDAO('TableStatsDAO');
$table_counts = $table_stats_dao->getTableRowCounts(); $table_counts = $table_stats_dao->getTableRowCounts();
if($table_counts[0]['count'] > UpgradeController::$WARN_TABLE_ROW_COUNT) { if ($table_counts[0]['count'] > UpgradeController::$WARN_TABLE_ROW_COUNT) {
$this->addToView('high_table_row_count',$table_counts[0]); $this->addToView('high_table_row_count',$table_counts[0]);
} }
try { try {
$backup_dao = DAOFactory::getDAO('BackupDAO'); $backup_dao = DAOFactory::getDAO('BackupDAO');
if (isset($_GET['backup'])) { if (isset($_GET['backup'])) {
self::mutexLock(); self::mutexLock();
/* export/download backup file */ try {
$backup_dao->export(); /* export/download backup file */
if ( ! headers_sent() ) { // this is so our test don't barf on us $backup_dao->export();
header('Content-Type: application/zip'); if ( ! headers_sent() ) { // this is so our test don't barf on us
header('Content-Disposition: attachment; filename="thinkup_db_backup.zip"'); header('Content-Type: application/zip');
header('Pragma: no-cache'); header('Content-Disposition: attachment; filename="thinkup_db_backup.zip"');
header('Expires: 0'); header('Pragma: no-cache');
} header('Expires: 0');
$fh = fopen($this->backup_file, "rb");
if ($fh) {
while (!feof($fh)) {
$data = fread($fh, 256);
echo $data;
flush();
} }
fclose($fh); $fh = fopen($this->backup_file, "rb");
unlink($this->backup_file); if ($fh) {
} else { while (!feof($fh)) {
throw new Exception("Unable to read backup zip file: " + $this->backup_file); $data = fread($fh, 256);
echo $data;
flush();
}
fclose($fh);
unlink($this->backup_file);
} else {
throw new Exception("Unable to read backup zip file: " + $this->backup_file);
}
} catch (MySQLGrantException $e) {
$this->addErrorMessage('It looks like the MySQL user does not have the proper permissions to grant'
. ' export Access. Please see the'
. ' <a href="http://thinkup.readthedocs.org/en/latest/troubleshoot/common/backupcannotwrite.html">'
. ' ThinkUp documentation</a> for more info on how to resolve this issue.');
self::mutexLock(true);
return $this->generateView();
} catch (OpenFileException $e) {
$this->addErrorMessage('It looks like the MySQL user does not have the proper file permissions to'
. ' export data. Please see the'
. ' <a href="http://thinkup.readthedocs.org/en/latest/troubleshoot/common/backupcannotwrite.html">'
. ' ThinkUp documentation</a> for more info on how to resolve this issue.');
self::mutexLock(true);
return $this->generateView();
} }
self::mutexLock(true); self::mutexLock(true);
} else if (isset($_FILES['backup_file'])) { } else if (isset($_FILES['backup_file'])) {
Expand Down
42 changes: 28 additions & 14 deletions webapp/_lib/controller/class.ExportServiceUserDataController.php
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public function __construct($session_started=false) {


public function adminControl() { public function adminControl() {
$this->disableCaching(); $this->disableCaching();
if (!BackupController::checkForZipSupport()) { if (! BackupController::checkForZipSupport()) {
$this->addToView('no_zip_support', true); $this->addToView('no_zip_support', true);
} else { } else {
$instance_dao = DAOFactory::getDAO('InstanceDAO'); $instance_dao = DAOFactory::getDAO('InstanceDAO');
Expand All @@ -84,7 +84,9 @@ public function adminControl() {
'describes how to import that data into an existing ThinkUp installation. 'describes how to import that data into an existing ThinkUp installation.
'); ');
self::exportData($instance->network_username, $instance->network); if (! self::exportData($instance->network_username, $instance->network)) {
return $this->generateView();
}
self::generateZipFile(); self::generateZipFile();
} else { } else {
$this->addErrorMessage('Invalid service user'); $this->addErrorMessage('Invalid service user');
Expand All @@ -107,25 +109,37 @@ protected function exportData($username, $service) {
$import_instructions = "To import your data, run the following commands in MySQL after you make two tweaks: $import_instructions = "To import your data, run the following commands in MySQL after you make two tweaks:
1. In each command, where it says INTO TABLE tu_destination, replace tu_destination with the name of your destination ". 1. In each command, where it says INTO TABLE tu_destination, replace tu_destination with the name of your destination ".
"table. "table.
2. Replace /your/path/to/data.tmp to your actual file path. Make sure the data.tmp files and their enclosing ". 2. Replace /your/path/to/data.tmp to your actual file path. Make sure the data.tmp files and their enclosing ".
"folder have the appropriate permissions for mysql to read the file. (Otherwise you'll get a ". "folder have the appropriate permissions for mysql to read the file. (Otherwise you'll get a ".
"'ERROR 13 (HY000): Can't get stat of' error.) "'ERROR 13 (HY000): Can't get stat of' error.)
Commands to run: Commands to run:
"; ";
self::appendToReadme($import_instructions); try {

//begin export
//get user id (some export methods need it) self::appendToReadme($import_instructions);
$user_dao = DAOFactory::getDAO('UserDAO');
$user = $user_dao->getUserByName($username, $service); //get user id (some export methods need it)
$user_id = $user->user_id; $user_dao = DAOFactory::getDAO('UserDAO');

$user = $user_dao->getUserByName($username, $service);
//begin export $user_id = $user->user_id;
self::exportPostsRepliesRetweetsFavoritesMentions($username, $user_id, $service);
self::exportFollowsAndFollowers($user_id, $service); self::exportPostsRepliesRetweetsFavoritesMentions($username, $user_id, $service);
self::exportFollowerCountHistory($user_id, $service); self::exportFollowsAndFollowers($user_id, $service);
self::exportFollowerCountHistory($user_id, $service);
return true;
} catch(Exception $e) {
$err = $e->getMessage();
if (preg_match("/Can't create\/write to file/", $err) || preg_match("/Can\'t get stat of/", $err)) {
// a file open perm issue?
$this->addToView('mysql_file_perms', true);
} else {
$this->addToView('grant_perms', true);
}
return false;
}
} }


protected function generateZipFile() { protected function generateZipFile() {
Expand Down
Loading

0 comments on commit ee0be8d

Please sign in to comment.