Permalink
Browse files

Check MySQL permissions for backup

* 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...
1 parent 3f593bc commit ee0be8d3ee26150561ac83b365bbb733b17267be Mark Wilkie committed with ginatrapani Nov 4, 2011
View
30 docs/source/troubleshoot/common/backupcannotwrite.rst
@@ -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
-create/write to file '<thinkup>/webapp/_lib/view/compiled_view/tu_encoded_locations.txt' (Errcode: 13)``
+``It looks like the MySQL user does not have the proper permissions to grant back up/export access``
-ThinkUp's backup tool doesn't have the permissions it needs to back up your files. Make sure that the mysql user
-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
-parent folders up the tree which would keep the mysql user from writing to that directory.
+or
+
+``It looks like the MySQL user does not have the proper file permissions to back up/export data``
+
+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.
View
2 docs/source/troubleshoot/messages/index.rst
@@ -11,3 +11,5 @@ This is a working list of ThinkUp's error, information, and success messages, an
upgrading
crawlinprogress
slickgridonlyshowing
+ mysqlgrant
+ mysqlfile
View
5 docs/source/troubleshoot/messages/mysqlfile.rst
@@ -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.
View
6 docs/source/troubleshoot/messages/mysqlgrant.rst
@@ -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>`_.
View
50 tests/TestOfBackupController.php
@@ -105,8 +105,7 @@ public function testLoadBackupViewCLIWarn() {
$old_count = UpgradeController::$WARN_TABLE_ROW_COUNT;
UpgradeController::$WARN_TABLE_ROW_COUNT = 2;
$results = $controller->control();
- $this->assertPattern('/we recommend that you use the.*command line backup tool.*when backing up ThinkUp/sm',
- $results);
+ $this->assertPattern('/we recommend that you use the/', $results);
$table_counts = $v_mgr->getTemplateDataItem('high_table_row_count');
$this->assertNotNull($table_counts);
$this->assertNotNull(3, $table_counts['count']); // tu_plugins, defaults to three
@@ -238,4 +237,51 @@ public function testRestore() {
$this->assertEqual($data['id'], 1);
$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");
+ }
}
View
41 tests/TestOfExportServiceUserDataController.php
@@ -27,6 +27,7 @@
require_once dirname(__FILE__).'/init.tests.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/_lib/model/class.ExportMySQLDAO.php';
class TestOfExportServiceUserDataController extends ThinkUpUnitTestCase {
@@ -150,4 +151,44 @@ public function testExport() {
$this->assertTrue($zip_files["/favorites.tmp"]);
$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");
+ }
+}
View
1 tests/TestOfUpgradeController.php
@@ -123,6 +123,7 @@ public function testDatabaseMigrationNeededHighTableCount() {
$this->simulateLogin('me@example.com', true);
$controller = new UpgradeController(true);
$results = $controller->go();
+
$this->assertPattern('/needs 1 database update/', $results);
$v_mgr = $controller->getViewManager();
$this->assertNull($v_mgr->getTemplateDataItem('high_table_row_count') ) ;
View
54 webapp/_lib/controller/class.BackupController.php
@@ -53,32 +53,48 @@ public function adminControl() {
// pass the count of the table with the most records
$table_stats_dao = DAOFactory::getDAO('TableStatsDAO');
$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]);
}
try {
$backup_dao = DAOFactory::getDAO('BackupDAO');
if (isset($_GET['backup'])) {
self::mutexLock();
- /* export/download backup file */
- $backup_dao->export();
- if ( ! headers_sent() ) { // this is so our test don't barf on us
- header('Content-Type: application/zip');
- header('Content-Disposition: attachment; filename="thinkup_db_backup.zip"');
- 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();
+ try {
+ /* export/download backup file */
+ $backup_dao->export();
+ if ( ! headers_sent() ) { // this is so our test don't barf on us
+ header('Content-Type: application/zip');
+ header('Content-Disposition: attachment; filename="thinkup_db_backup.zip"');
+ header('Pragma: no-cache');
+ header('Expires: 0');
}
- fclose($fh);
- unlink($this->backup_file);
- } else {
- throw new Exception("Unable to read backup zip file: " + $this->backup_file);
+ $fh = fopen($this->backup_file, "rb");
+ if ($fh) {
+ while (!feof($fh)) {
+ $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);
} else if (isset($_FILES['backup_file'])) {
View
42 webapp/_lib/controller/class.ExportServiceUserDataController.php
@@ -63,7 +63,7 @@ public function __construct($session_started=false) {
public function adminControl() {
$this->disableCaching();
- if (!BackupController::checkForZipSupport()) {
+ if (! BackupController::checkForZipSupport()) {
$this->addToView('no_zip_support', true);
} else {
$instance_dao = DAOFactory::getDAO('InstanceDAO');
@@ -84,7 +84,9 @@ public function adminControl() {
'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();
} else {
$this->addErrorMessage('Invalid service user');
@@ -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:
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 ".
"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.)
Commands to run:
";
- self::appendToReadme($import_instructions);
-
- //get user id (some export methods need it)
- $user_dao = DAOFactory::getDAO('UserDAO');
- $user = $user_dao->getUserByName($username, $service);
- $user_id = $user->user_id;
-
- //begin export
- self::exportPostsRepliesRetweetsFavoritesMentions($username, $user_id, $service);
- self::exportFollowsAndFollowers($user_id, $service);
- self::exportFollowerCountHistory($user_id, $service);
+ try {
+ //begin export
+ self::appendToReadme($import_instructions);
+
+ //get user id (some export methods need it)
+ $user_dao = DAOFactory::getDAO('UserDAO');
+ $user = $user_dao->getUserByName($username, $service);
+ $user_id = $user->user_id;
+
+ self::exportPostsRepliesRetweetsFavoritesMentions($username, $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() {
View
61 webapp/_lib/model/class.BackupMySQLDAO.php
@@ -95,7 +95,7 @@ public function import($zipfile) {
foreach($db_tables as $table) {
foreach($table as $key => $value) {
$table_name = $value;
- if(! isset( $imported_tables[ $table_name ] ) ) {
+ if (! isset( $imported_tables[ $table_name ] ) ) {
$stmt = $this->execute("DROP TABLE IF EXISTS $table_name");
}
}
@@ -141,32 +141,45 @@ public function export($backup_file = null) {
$table_locks_list .= $value . ' WRITE';
}
}
- $stmt = $this->execute("LOCK TABLES " . $table_locks_list);
- $tmp_table_files = array();
- foreach($data as $table) {
- foreach($table as $key => $value) {
- if (getenv('BACKUP_VERBOSE')!==false) {
- print " Backing up data for table: $value\n";
- }
- $stmt = $this->execute($q2 . $value);
- $create_tables .= "-- Create $value table statement\n";
- $create_tables .= "DROP TABLE IF EXISTS $value;\n";
- $create_data = $this->getDataRowAsArray($stmt);
- $create_tables .= $create_data["Create Table"] . ";";
- $create_tables .= "\n\n";
+ try {
+ $stmt = $this->execute("LOCK TABLES " . $table_locks_list);
+ $tmp_table_files = array();
+ foreach($data as $table) {
+ foreach($table as $key => $value) {
+ if (getenv('BACKUP_VERBOSE')!==false) {
+ print " Backing up data for table: $value\n";
+ }
+ $stmt = $this->execute($q2 . $value);
+ $create_tables .= "-- Create $value table statement\n";
+ $create_tables .= "DROP TABLE IF EXISTS $value;\n";
+ $create_data = $this->getDataRowAsArray($stmt);
+ $create_tables .= $create_data["Create Table"] . ";";
+ $create_tables .= "\n\n";
- // export table data
- $table_file = THINKUP_WEBAPP_PATH . self::CACHE_DIR . '/' . $value . '.txt';
- if (file_exists($table_file)) {
- unlink($table_file);
+ // export table data
+ $table_file = THINKUP_WEBAPP_PATH . self::CACHE_DIR . '/' . $value . '.txt';
+ if (file_exists($table_file)) {
+ unlink($table_file);
+ }
+ $q3 = "select * INTO OUTFILE '$table_file' from $value";
+ $stmt = $this->execute($q3);
+ $zip->addFile($table_file,"/$value" . '.txt');
+ array_push($tmp_table_files, $table_file);
}
- $q3 = "select * INTO OUTFILE '$table_file' from $value";
- $stmt = $this->execute($q3);
- $zip->addFile($table_file,"/$value" . '.txt');
- array_push($tmp_table_files, $table_file);
}
+ } 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 perm issue?
+ throw new OpenFileException("It looks like the MySQL user does not have the proper file permissions "
+ . "to back up data");
+ } else {
+ // assume its a GRANT FILE OR LOCK TABLES issue?
+ throw new MySQLGrantException("It looks like the MySQL user does not have the proper grant permissions "
+ . "to back up data");
+ }
+ error_log("export DB OUTFILE error: " . $e->getMessage());
}
-
// unlock tables...
$stmt = $this->execute("unlock tables");
if (getenv('BACKUP_VERBOSE')!==false) {
@@ -179,7 +192,7 @@ public function export($backup_file = null) {
unlink($tmp_file);
}
if ($zip_close_status == false) {
- throw new Exception("Unable to create backup file for exporting, bad file path?: $zip_file");
+ throw new Exception("Unable to create backup file for exporting. Bad file path?: $zip_file");
}
return $zip_file;
}
View
31 webapp/_lib/model/exceptions/class.MySQLGrantException.php
@@ -0,0 +1,31 @@
+<?php
+/**
+ *
+ * ThinkUp/webapp/_lib/model/exceptions/class.MySQLGrantException.php
+ *
+ * Copyright (c) 2011 Mark Wilkie
+ *
+ * LICENSE:
+ *
+ * This file is part of ThinkUp (http://thinkupapp.com).
+ *
+ * ThinkUp is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any
+ * later version.
+ *
+ * ThinkUp is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with ThinkUp. If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * @author Mark Wilkie <mwilkie@gmail.com>
+ * @license http://www.gnu.org/licenses/gpl.html
+ * @copyright 2011 Mark Wilkie
+ */
+class MySQLGrantException extends Exception {
+ public function __construct($message) {
+ parent::__construct($message);
+ }
+}
View
20 webapp/_lib/view/install.exportserviceuser.tpl
@@ -20,6 +20,24 @@
through this interface.
</p>
</div>
+{elseif $mysql_file_perms}
+<div class="alert urgent" style="margin-top: 10px; padding: 0.5em 0.7em;">
+ <p>
+ <span class="ui-icon ui-icon-info" style="float: left; margin: 0.3em 0.3em 0pt 0pt;"></span>
+ 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/messages/mysqlfile.rst">ThinkUp
+ documentation</a> for more info on how to resolve this issue.
+ </p>
+</div>
+{elseif $grant_perms}
+<div class="alert urgent" style="margin-top: 10px; padding: 0.5em 0.7em;">
+ <p>
+ <span class="ui-icon ui-icon-info" style="float: left; margin: 0.3em 0.3em 0pt 0pt;"></span>
+ It looks like the MySQL user does not have the proper permissions to export data. Please see the
+ <a href="http://thinkup.readthedocs.org/en/latest/troubleshoot/messages/mysqlgrant.rst">ThinkUp
+ documentation</a> for more info on how to resolve this issue.
+ </p>
+</div>
{else}
<div class="">
{if $messages}
@@ -48,4 +66,4 @@
</div>
</div>
</div>
-{include file="_install.footer.tpl"}
+{include file="_install.footer.tpl"}

0 comments on commit ee0be8d

Please sign in to comment.