Browse Source
Remove obsolete RepairLegacyStorages repair step
Remove obsolete RepairLegacyStorages repair step
Signed-off-by: Lukas Reschke <lukas@statuscode.ch>pull/2765/head
committed by
Lukas Reschke
No known key found for this signature in database
GPG Key ID: B9F6980CF6E759B1
3 changed files with 0 additions and 580 deletions
-
2lib/private/Repair.php
-
257lib/private/Repair/RepairLegacyStorages.php
-
321tests/lib/Repair/RepairLegacyStoragesTest.php
@ -1,257 +0,0 @@ |
|||
<?php |
|||
/** |
|||
* @copyright Copyright (c) 2016, ownCloud, Inc. |
|||
* |
|||
* @author Aaron Wood <aaronjwood@gmail.com> |
|||
* @author Joas Schilling <coding@schilljs.com> |
|||
* @author Morris Jobke <hey@morrisjobke.de> |
|||
* @author Thomas Müller <thomas.mueller@tmit.eu> |
|||
* @author Vincent Petry <pvince81@owncloud.com> |
|||
* |
|||
* @license AGPL-3.0 |
|||
* |
|||
* This code is free software: you can redistribute it and/or modify |
|||
* it under the terms of the GNU Affero General Public License, version 3, |
|||
* as published by the Free Software Foundation. |
|||
* |
|||
* This program 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 Affero General Public License for more details. |
|||
* |
|||
* You should have received a copy of the GNU Affero General Public License, version 3, |
|||
* along with this program. If not, see <http://www.gnu.org/licenses/> |
|||
* |
|||
*/ |
|||
|
|||
namespace OC\Repair; |
|||
|
|||
use OC\Files\Cache\Storage; |
|||
use OC\RepairException; |
|||
use OCP\Migration\IOutput; |
|||
use OCP\Migration\IRepairStep; |
|||
|
|||
class RepairLegacyStorages implements IRepairStep{ |
|||
/** |
|||
* @var \OCP\IConfig |
|||
*/ |
|||
protected $config; |
|||
|
|||
/** |
|||
* @var \OCP\IDBConnection |
|||
*/ |
|||
protected $connection; |
|||
|
|||
protected $findStorageInCacheStatement; |
|||
protected $renameStorageStatement; |
|||
|
|||
/** |
|||
* @param \OCP\IConfig $config |
|||
* @param \OCP\IDBConnection $connection |
|||
*/ |
|||
public function __construct($config, $connection) { |
|||
$this->connection = $connection; |
|||
$this->config = $config; |
|||
|
|||
$this->findStorageInCacheStatement = $this->connection->prepare( |
|||
'SELECT DISTINCT `storage` FROM `*PREFIX*filecache`' |
|||
. ' WHERE `storage` in (?, ?)' |
|||
); |
|||
$this->renameStorageStatement = $this->connection->prepare( |
|||
'UPDATE `*PREFIX*storages`' |
|||
. ' SET `id` = ?' |
|||
. ' WHERE `id` = ?' |
|||
); |
|||
} |
|||
|
|||
public function getName() { |
|||
return 'Repair legacy storages'; |
|||
} |
|||
|
|||
/** |
|||
* Extracts the user id from a legacy storage id |
|||
* |
|||
* @param string $storageId legacy storage id in the |
|||
* format "local::/path/to/datadir/userid" |
|||
* @return string user id extracted from the storage id |
|||
*/ |
|||
private function extractUserId($storageId) { |
|||
$storageId = rtrim($storageId, '/'); |
|||
$pos = strrpos($storageId, '/'); |
|||
return substr($storageId, $pos + 1); |
|||
} |
|||
|
|||
/** |
|||
* Fix the given legacy storage by renaming the old id |
|||
* to the new id. If the new id already exists, whichever |
|||
* storage that has data in the file cache will be used. |
|||
* If both have data, nothing will be done and false is |
|||
* returned. |
|||
* |
|||
* @param string $oldId old storage id |
|||
* @param int $oldNumericId old storage numeric id |
|||
* @param string $userId |
|||
* @return bool true if fixed, false otherwise |
|||
* @throws RepairException |
|||
*/ |
|||
private function fixLegacyStorage($oldId, $oldNumericId, $userId = null) { |
|||
// check whether the new storage already exists
|
|||
if (is_null($userId)) { |
|||
$userId = $this->extractUserId($oldId); |
|||
} |
|||
$newId = 'home::' . $userId; |
|||
|
|||
// check if target id already exists
|
|||
$newNumericId = Storage::getNumericStorageId($newId); |
|||
if (!is_null($newNumericId)) { |
|||
$newNumericId = (int)$newNumericId; |
|||
// try and resolve the conflict
|
|||
// check which one of "local::" or "home::" needs to be kept
|
|||
$this->findStorageInCacheStatement->execute(array($oldNumericId, $newNumericId)); |
|||
$row1 = $this->findStorageInCacheStatement->fetch(); |
|||
$row2 = $this->findStorageInCacheStatement->fetch(); |
|||
$this->findStorageInCacheStatement->closeCursor(); |
|||
if ($row2 !== false) { |
|||
// two results means both storages have data, not auto-fixable
|
|||
throw new RepairException( |
|||
'Could not automatically fix legacy storage ' |
|||
. '"' . $oldId . '" => "' . $newId . '"' |
|||
. ' because they both have data.' |
|||
); |
|||
} |
|||
if ($row1 === false || (int)$row1['storage'] === $oldNumericId) { |
|||
// old storage has data, then delete the empty new id
|
|||
$toDelete = $newId; |
|||
} else if ((int)$row1['storage'] === $newNumericId) { |
|||
// new storage has data, then delete the empty old id
|
|||
$toDelete = $oldId; |
|||
} else { |
|||
// unknown case, do not continue
|
|||
return false; |
|||
} |
|||
|
|||
// delete storage including file cache
|
|||
Storage::remove($toDelete); |
|||
|
|||
// if we deleted the old id, the new id will be used
|
|||
// automatically
|
|||
if ($toDelete === $oldId) { |
|||
// nothing more to do
|
|||
return true; |
|||
} |
|||
} |
|||
|
|||
// rename old id to new id
|
|||
$newId = Storage::adjustStorageId($newId); |
|||
$oldId = Storage::adjustStorageId($oldId); |
|||
$rowCount = $this->renameStorageStatement->execute(array($newId, $oldId)); |
|||
$this->renameStorageStatement->closeCursor(); |
|||
return ($rowCount === 1); |
|||
} |
|||
|
|||
/** |
|||
* Converts legacy home storage ids in the format |
|||
* "local::/data/dir/path/userid/" to the new format "home::userid" |
|||
*/ |
|||
public function run(IOutput $out) { |
|||
// only run once
|
|||
if ($this->config->getAppValue('core', 'repairlegacystoragesdone') === 'yes') { |
|||
return; |
|||
} |
|||
|
|||
$dataDir = $this->config->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data/'); |
|||
$dataDir = rtrim($dataDir, '/') . '/'; |
|||
$dataDirId = 'local::' . $dataDir; |
|||
|
|||
$count = 0; |
|||
$hasWarnings = false; |
|||
|
|||
$this->connection->beginTransaction(); |
|||
|
|||
// note: not doing a direct UPDATE with the REPLACE function
|
|||
// because regexp search/extract is needed and it is not guaranteed
|
|||
// to work on all database types
|
|||
$sql = 'SELECT `id`, `numeric_id` FROM `*PREFIX*storages`' |
|||
. ' WHERE `id` LIKE ?' |
|||
. ' ORDER BY `id`'; |
|||
$result = $this->connection->executeQuery($sql, array($this->connection->escapeLikeParameter($dataDirId) . '%')); |
|||
|
|||
while ($row = $result->fetch()) { |
|||
$currentId = $row['id']; |
|||
// one entry is the datadir itself
|
|||
if ($currentId === $dataDirId) { |
|||
continue; |
|||
} |
|||
|
|||
try { |
|||
if ($this->fixLegacyStorage($currentId, (int)$row['numeric_id'])) { |
|||
$count++; |
|||
} |
|||
} |
|||
catch (RepairException $e) { |
|||
$hasWarnings = true; |
|||
$out->warning('Could not repair legacy storage ' . $currentId . ' automatically.'); |
|||
} |
|||
} |
|||
|
|||
// check for md5 ids, not in the format "prefix::"
|
|||
$sql = 'SELECT COUNT(*) AS "c" FROM `*PREFIX*storages`' |
|||
. ' WHERE `id` NOT LIKE \'%::%\''; |
|||
$result = $this->connection->executeQuery($sql); |
|||
$row = $result->fetch(); |
|||
|
|||
// find at least one to make sure it's worth
|
|||
// querying the user list
|
|||
if ((int)$row['c'] > 0) { |
|||
$userManager = \OC::$server->getUserManager(); |
|||
|
|||
// use chunks to avoid caching too many users in memory
|
|||
$limit = 30; |
|||
$offset = 0; |
|||
|
|||
do { |
|||
// query the next page of users
|
|||
$results = $userManager->search('', $limit, $offset); |
|||
$storageIds = array(); |
|||
foreach ($results as $uid => $userObject) { |
|||
$storageId = $dataDirId . $uid . '/'; |
|||
if (strlen($storageId) <= 64) { |
|||
// skip short storage ids as they were handled in the previous section
|
|||
continue; |
|||
} |
|||
$storageIds[$uid] = $storageId; |
|||
} |
|||
|
|||
if (count($storageIds) > 0) { |
|||
// update the storages of these users
|
|||
foreach ($storageIds as $uid => $storageId) { |
|||
$numericId = Storage::getNumericStorageId($storageId); |
|||
try { |
|||
if (!is_null($numericId) && $this->fixLegacyStorage($storageId, (int)$numericId)) { |
|||
$count++; |
|||
} |
|||
} |
|||
catch (RepairException $e) { |
|||
$hasWarnings = true; |
|||
$out->warning('Could not repair legacy storage ' . $storageId . ' automatically.'); |
|||
} |
|||
} |
|||
} |
|||
$offset += $limit; |
|||
} while (count($results) >= $limit); |
|||
} |
|||
|
|||
$out->info('Updated ' . $count . ' legacy home storage ids'); |
|||
|
|||
$this->connection->commit(); |
|||
|
|||
Storage::getGlobalCache()->clearCache(); |
|||
|
|||
if ($hasWarnings) { |
|||
$out->warning('Some legacy storages could not be repaired. Please manually fix them then re-run ./occ maintenance:repair'); |
|||
} else { |
|||
// if all were done, no need to redo the repair during next upgrade
|
|||
$this->config->setAppValue('core', 'repairlegacystoragesdone', 'yes'); |
|||
} |
|||
} |
|||
} |
|||
@ -1,321 +0,0 @@ |
|||
<?php |
|||
/** |
|||
* Copyright (c) 2014 Vincent Petry <pvince81@owncloud.com> |
|||
* This file is licensed under the Affero General Public License version 3 or |
|||
* later. |
|||
* See the COPYING-README file. |
|||
*/ |
|||
|
|||
namespace Test\Repair; |
|||
|
|||
use OC\Files\Cache\Cache; |
|||
use OC\Files\Cache\Storage; |
|||
use OCP\Migration\IOutput; |
|||
use PHPUnit_Framework_MockObject_MockObject; |
|||
use Test\TestCase; |
|||
|
|||
/** |
|||
* Tests for the converting of legacy storages to home storages. |
|||
* |
|||
* @group DB |
|||
* |
|||
* @see \OC\Repair\RepairLegacyStorages |
|||
*/ |
|||
class RepairLegacyStoragesTest extends TestCase { |
|||
/** @var \OCP\IDBConnection */ |
|||
private $connection; |
|||
/** @var \OCP\IConfig */ |
|||
private $config; |
|||
private $user; |
|||
/** @var \OC\Repair\RepairLegacyStorages */ |
|||
private $repair; |
|||
|
|||
private $dataDir; |
|||
private $oldDataDir; |
|||
|
|||
private $legacyStorageId; |
|||
private $newStorageId; |
|||
|
|||
/** @var IOutput | PHPUnit_Framework_MockObject_MockObject */ |
|||
private $outputMock; |
|||
|
|||
protected function setUp() { |
|||
parent::setUp(); |
|||
|
|||
$this->config = \OC::$server->getConfig(); |
|||
$this->connection = \OC::$server->getDatabaseConnection(); |
|||
$this->oldDataDir = $this->config->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data/'); |
|||
|
|||
$this->repair = new \OC\Repair\RepairLegacyStorages($this->config, $this->connection); |
|||
|
|||
$this->outputMock = $this->getMockBuilder('\OCP\Migration\IOutput') |
|||
->disableOriginalConstructor() |
|||
->getMock(); |
|||
} |
|||
|
|||
protected function tearDown() { |
|||
$user = \OC::$server->getUserManager()->get($this->user); |
|||
if ($user) { |
|||
$user->delete(); |
|||
} |
|||
|
|||
$sql = 'DELETE FROM `*PREFIX*storages`'; |
|||
$this->connection->executeQuery($sql); |
|||
$sql = 'DELETE FROM `*PREFIX*filecache`'; |
|||
$this->connection->executeQuery($sql); |
|||
$this->config->setSystemValue('datadirectory', $this->oldDataDir); |
|||
$this->config->setAppValue('core', 'repairlegacystoragesdone', 'no'); |
|||
|
|||
parent::tearDown(); |
|||
} |
|||
|
|||
/** |
|||
* @param string $dataDir |
|||
* @param string $userId |
|||
* @throws \Exception |
|||
*/ |
|||
function prepareSettings($dataDir, $userId) { |
|||
// hard-coded string as we want a predictable fixed length
|
|||
// no data will be written there
|
|||
$this->dataDir = $dataDir; |
|||
$this->config->setSystemValue('datadirectory', $this->dataDir); |
|||
|
|||
$this->user = $userId; |
|||
$this->legacyStorageId = 'local::' . $this->dataDir . $this->user . '/'; |
|||
$this->newStorageId = 'home::' . $this->user; |
|||
\OC::$server->getUserManager()->createUser($this->user, $this->user); |
|||
} |
|||
|
|||
/** |
|||
* Create a storage entry |
|||
* |
|||
* @param string $storageId |
|||
* @return int |
|||
*/ |
|||
private function createStorage($storageId) { |
|||
$sql = 'INSERT INTO `*PREFIX*storages` (`id`)' |
|||
. ' VALUES (?)'; |
|||
|
|||
$storageId = Storage::adjustStorageId($storageId); |
|||
$numRows = $this->connection->executeUpdate($sql, array($storageId)); |
|||
$this->assertSame(1, $numRows); |
|||
|
|||
return (int)\OC::$server->getDatabaseConnection()->lastInsertId('*PREFIX*storages'); |
|||
} |
|||
|
|||
/** |
|||
* Create dummy data in the filecache for the given storage numeric id |
|||
* |
|||
* @param string $storageId storage id |
|||
*/ |
|||
private function createData($storageId) { |
|||
$cache = new Cache($storageId); |
|||
$cache->put( |
|||
'dummyfile.txt', |
|||
array('size' => 5, 'mtime' => 12, 'mimetype' => 'text/plain') |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* Test that existing home storages are left alone when valid. |
|||
* |
|||
* @dataProvider settingsProvider |
|||
* |
|||
* @param string $dataDir |
|||
* @param string $userId |
|||
*/ |
|||
public function testNoopWithExistingHomeStorage($dataDir, $userId) { |
|||
$this->prepareSettings($dataDir, $userId); |
|||
$newStorageNumId = $this->createStorage($this->newStorageId); |
|||
|
|||
$this->repair->run($this->outputMock); |
|||
|
|||
$this->assertNull(Storage::getNumericStorageId($this->legacyStorageId)); |
|||
$this->assertSame($newStorageNumId, Storage::getNumericStorageId($this->newStorageId)); |
|||
} |
|||
|
|||
/** |
|||
* Test that legacy storages are converted to home storages when |
|||
* the latter does not exist. |
|||
* |
|||
* @dataProvider settingsProvider |
|||
* |
|||
* @param string $dataDir |
|||
* @param string $userId |
|||
*/ |
|||
public function testConvertLegacyToHomeStorage($dataDir, $userId) { |
|||
$this->prepareSettings($dataDir, $userId); |
|||
$legacyStorageNumId = $this->createStorage($this->legacyStorageId); |
|||
|
|||
$this->repair->run($this->outputMock); |
|||
|
|||
$this->assertNull(Storage::getNumericStorageId($this->legacyStorageId)); |
|||
$this->assertSame($legacyStorageNumId, Storage::getNumericStorageId($this->newStorageId)); |
|||
} |
|||
|
|||
/** |
|||
* Test that legacy storages are converted to home storages |
|||
* when home storage already exists but has no data. |
|||
* |
|||
* @dataProvider settingsProvider |
|||
* |
|||
* @param string $dataDir |
|||
* @param string $userId |
|||
*/ |
|||
public function testConvertLegacyToExistingEmptyHomeStorage($dataDir, $userId) { |
|||
$this->prepareSettings($dataDir, $userId); |
|||
$legacyStorageNumId = $this->createStorage($this->legacyStorageId); |
|||
$this->createStorage($this->newStorageId); |
|||
|
|||
$this->createData($this->legacyStorageId); |
|||
|
|||
$this->repair->run($this->outputMock); |
|||
|
|||
$this->assertNull(Storage::getNumericStorageId($this->legacyStorageId)); |
|||
$this->assertSame($legacyStorageNumId, Storage::getNumericStorageId($this->newStorageId)); |
|||
} |
|||
|
|||
/** |
|||
* Test that legacy storages are converted to home storages |
|||
* when home storage already exists and the legacy storage |
|||
* has no data. |
|||
* |
|||
* @dataProvider settingsProvider |
|||
* |
|||
* @param string $dataDir |
|||
* @param string $userId |
|||
*/ |
|||
public function testConvertEmptyLegacyToHomeStorage($dataDir, $userId) { |
|||
$this->prepareSettings($dataDir, $userId); |
|||
$this->createStorage($this->legacyStorageId); |
|||
$newStorageNumId = $this->createStorage($this->newStorageId); |
|||
|
|||
$this->createData($this->newStorageId); |
|||
|
|||
$this->repair->run($this->outputMock); |
|||
|
|||
$this->assertNull(Storage::getNumericStorageId($this->legacyStorageId)); |
|||
$this->assertSame($newStorageNumId, Storage::getNumericStorageId($this->newStorageId)); |
|||
} |
|||
|
|||
/** |
|||
* Test that nothing is done when both conflicting legacy |
|||
* and home storage have data. |
|||
* |
|||
* @dataProvider settingsProvider |
|||
* |
|||
* @param string $dataDir |
|||
* @param string $userId |
|||
*/ |
|||
public function testConflictNoop($dataDir, $userId) { |
|||
$this->prepareSettings($dataDir, $userId); |
|||
$legacyStorageNumId = $this->createStorage($this->legacyStorageId); |
|||
$newStorageNumId = $this->createStorage($this->newStorageId); |
|||
|
|||
$this->createData($this->legacyStorageId); |
|||
$this->createData($this->newStorageId); |
|||
|
|||
$this->outputMock->expects($this->exactly(2))->method('warning'); |
|||
$this->repair->run($this->outputMock); |
|||
|
|||
// storages left alone
|
|||
$this->assertSame($legacyStorageNumId, Storage::getNumericStorageId($this->legacyStorageId)); |
|||
$this->assertSame($newStorageNumId, Storage::getNumericStorageId($this->newStorageId)); |
|||
|
|||
// do not set the done flag
|
|||
$this->assertNotEquals('yes', $this->config->getAppValue('core', 'repairlegacystoragesdone')); |
|||
} |
|||
|
|||
/** |
|||
* Test that the data dir local entry is left alone |
|||
* |
|||
* @dataProvider settingsProvider |
|||
* |
|||
* @param string $dataDir |
|||
* @param string $userId |
|||
*/ |
|||
public function testDataDirEntryNoop($dataDir, $userId) { |
|||
$this->prepareSettings($dataDir, $userId); |
|||
$storageId = 'local::' . $this->dataDir; |
|||
$numId = $this->createStorage($storageId); |
|||
|
|||
$this->repair->run($this->outputMock); |
|||
|
|||
$this->assertSame($numId, Storage::getNumericStorageId($storageId)); |
|||
} |
|||
|
|||
/** |
|||
* Test that external local storages are left alone |
|||
* |
|||
* @dataProvider settingsProvider |
|||
* |
|||
* @param string $dataDir |
|||
* @param string $userId |
|||
*/ |
|||
public function testLocalExtStorageNoop($dataDir, $userId) { |
|||
$this->prepareSettings($dataDir, $userId); |
|||
$storageId = 'local::/tmp/somedir/' . $this->user; |
|||
$numId = $this->createStorage($storageId); |
|||
|
|||
$this->repair->run($this->outputMock); |
|||
|
|||
$this->assertSame($numId, Storage::getNumericStorageId($storageId)); |
|||
} |
|||
|
|||
/** |
|||
* Test that other external storages are left alone |
|||
* |
|||
* @dataProvider settingsProvider |
|||
* |
|||
* @param string $dataDir |
|||
* @param string $userId |
|||
*/ |
|||
public function testExtStorageNoop($dataDir, $userId) { |
|||
$this->prepareSettings($dataDir, $userId); |
|||
$storageId = 'smb::user@password/tmp/somedir/' . $this->user; |
|||
$numId = $this->createStorage($storageId); |
|||
|
|||
$this->repair->run($this->outputMock); |
|||
|
|||
$this->assertSame($numId, Storage::getNumericStorageId($storageId)); |
|||
} |
|||
|
|||
/** |
|||
* Provides data dir and user name |
|||
*/ |
|||
function settingsProvider() { |
|||
return array( |
|||
// regular data dir
|
|||
array( |
|||
'/tmp/oc-autotest/datadir/', |
|||
$this->getUniqueID('user_'), |
|||
), |
|||
// long datadir / short user
|
|||
array( |
|||
'/tmp/oc-autotest/datadir01234567890123456789012345678901234567890123456789END/', |
|||
$this->getUniqueID('user_'), |
|||
), |
|||
// short datadir / long user
|
|||
array( |
|||
'/tmp/oc-autotest/datadir/', |
|||
'u123456789012345678901234567890123456789012345678901234567890END', // 64 chars
|
|||
), |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* Only run the repair once |
|||
*/ |
|||
public function testOnlyRunOnce() { |
|||
$this->outputMock->expects($this->exactly(1))->method('info'); |
|||
|
|||
$this->prepareSettings('/tmp/oc-autotest/datadir', $this->getUniqueID('user_')); |
|||
$this->assertNotEquals('yes', $this->config->getAppValue('core', 'repairlegacystoragesdone')); |
|||
$this->repair->run($this->outputMock); |
|||
$this->assertEquals('yes', $this->config->getAppValue('core', 'repairlegacystoragesdone')); |
|||
|
|||
$this->outputMock->expects($this->never())->method('info'); |
|||
$this->repair->run($this->outputMock); |
|||
$this->assertEquals('yes', $this->config->getAppValue('core', 'repairlegacystoragesdone')); |
|||
} |
|||
} |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue