Skip to content

Commit

Permalink
Add restore support for farm
Browse files Browse the repository at this point in the history
  • Loading branch information
it-spiderman authored and osnard committed Nov 8, 2024
1 parent 8d69282 commit fa61fac
Show file tree
Hide file tree
Showing 8 changed files with 304 additions and 102 deletions.
31 changes: 29 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ Also, you may want to exclude some data tables. It can be done that way:
```


### Backing up/restoring farming instance
### Backing up farming instance

Important: Database connection params, whether read from setting file or specified in `profile`, must refer to the
main (`w`) wiki database, as that DB holds information on all instances.
Expand Down Expand Up @@ -136,4 +136,31 @@ with `profile.json`:
}
```

will export all active instance of the farm, and then export `w` itself
will export all active instance of the farm, and then export `w` itself

### Restore wiki farm instance

**Only instances that were backed up using this tool can be restored safely!**

Since instance settings are stored in DB, when backing up, extra file `filesystem/settings.json` will be generated,
which is then used on restore.

When restoring a farm instance, profile file __must__ be used, with `db-options.connection` set
to the main wiki database. Optionally, set `farm-options.instances-dir` to the root directory that holds instances.

```json
{
"db-options": {
"connection": {
"dbserver": "127.0.0.1",
"dbuser": "root",
"dbpassword": "...",
"dbname": "w"
}
},
"farm-options": {
"instances-dir": "/path/to/instances"
}
}
```

4 changes: 3 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
"symfony/console": "v3.4.9",
"symfony/filesystem": "~5",
"ifsnop/mysqldump-php": "v2.4",
"ext-pdo": "*"
"ext-pdo": "*",
"ext-json": "*",
"ext-zip": "*"
},
"require-dev": {
"phpunit/phpunit": "^8.5"
Expand Down
67 changes: 44 additions & 23 deletions src/Commands/WikiBackup.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

use DateTime;
use Exception;
use MWStake\MediaWiki\CliAdm\FarmInstanceSettingsReader;
use MWStake\MediaWiki\CliAdm\FarmInstanceSettingsManager;
use MWStake\MediaWiki\CliAdm\IBackupProfile;
use MWStake\MediaWiki\CliAdm\JSONBackupProfile;
use MWStake\MediaWiki\CliAdm\DefaultBackupProfile;
Expand Down Expand Up @@ -82,6 +82,9 @@ class WikiBackup extends Command {
/** @var array */
private $skipDbPrefixes = [];

/** @var FarmInstanceSettingsManager|null */
private $farmSettingsReader = null;

/**
*
* @var ZipArchive
Expand Down Expand Up @@ -229,18 +232,18 @@ private function setupFarmEnvironment( array $options ) {
throw new Exception( "Could not connect to management database: " . $ex->getMessage() );
}

$settingsReader = new FarmInstanceSettingsReader( $mainPdo, $settingsTable );
$this->farmSettingsReader = new FarmInstanceSettingsManager( $mainPdo, $settingsTable );
if ( $this->instanceName === '*' ) {
$this->output->writeln( "Backing up all instances ..." );
// Backup all instances
$mainDbName = $this->dbname;
$mainDbPrefix = $this->dbprefix;
$originalMWRoot = $this->mediawikiRoot;
$activeInstances = $settingsReader->getAllActiveInstances();
$activeInstances = $this->farmSettingsReader->getAllActiveInstances();
foreach ( $activeInstances as $instanceName ) {
$this->instanceName = $instanceName;
$this->mediawikiRoot = "$instancesDir/$instanceName";
if ( $this->setupSingleFarmInstance( $settingsReader, $instanceName ) ) {
if ( $this->setupSingleFarmInstance( $this->farmSettingsReader, $instanceName ) ) {
try {
$this->doBackup();
} catch ( Exception $ex ) {
Expand All @@ -257,22 +260,21 @@ private function setupFarmEnvironment( array $options ) {
$this->dbprefix = $mainDbPrefix;
$this->mediawikiRoot = $originalMWRoot;
$this->isFarmContext = false;
$this->skipDbPrefixes = $settingsReader->getAllInstancePrefixes( $this->dbname );
$this->skipDbPrefixes = $this->farmSettingsReader->getAllInstancePrefixes( $this->dbname );
} else {
$this->mediawikiRoot = "$instancesDir/$this->instanceName";
if ( !$this->setupSingleFarmInstance( $settingsReader, $this->instanceName ) ) {
throw new Exception( "Could not read settings for instance '{$this->instanceName}'" );
if ( !$this->setupSingleFarmInstance( $this->instanceName ) ) {
throw new Exception( "Could not read settings for instance '$this->instanceName'" );
}
}
}

/**
* @param FarmInstanceSettingsReader $settingsReader
* @param string $instanceName
* @return bool
*/
private function setupSingleFarmInstance( FarmInstanceSettingsReader $settingsReader, string $instanceName ) {
$settings = $settingsReader->getSettings( $instanceName );
private function setupSingleFarmInstance( string $instanceName ) {
$settings = $this->farmSettingsReader->getSettings( $instanceName );
if ( !$settings ) {
return false;
}
Expand Down Expand Up @@ -378,10 +380,40 @@ protected function addImagesFolder() {
}

private function addCustomFilesAndFolders() {
$toBackup = $this->getCustomFilesToBackup();
$backupCount = count( $toBackup );
if ( $this->isFarmContext ) {
$backupCount++;
$settings = $this->farmSettingsReader->getFullSettings( $this->instanceName );
if ( $settings === null ) {
throw new Exception( "Could not read settings for instance '$this->instanceName'" );
}
$res = file_put_contents(
$this->mediawikiRoot . '/settings.json',
json_encode( $settings, JSON_PRETTY_PRINT )
);
if ( !$res ) {
throw new Exception( "Could not write instance settings file" );
}
$toBackup[] = $this->mediawikiRoot . '/settings.json';
}

$progressBar = new ProgressBar( $this->output, $backupCount );
$this->output->writeln( "Adding 'custom-paths' ..." );
foreach( $toBackup as $customFile ) {
$localPath = preg_replace( '#^' . preg_quote( $this->mediawikiRoot ) . '#', '', $customFile );
$this->zip->addFile( $customFile, "filesystem/$localPath" );
$progressBar->advance();
}
$progressBar->finish();
$this->output->write( "\n" );
}

private function getCustomFilesToBackup(): array {
$filesystemOptions = $this->profile->getFSBackupOptions();
$customPaths = $filesystemOptions['include-custom-paths'] ?? [];
if ( empty( $customPaths ) ) {
return;
return [];
}
$customFilesToBackup = [];
foreach( $customPaths as $customPath ) {
Expand All @@ -404,18 +436,7 @@ private function addCustomFilesAndFolders() {
$customFilesToBackup[] = $fileInfo->getPathname();
}
}
$progressBar = new ProgressBar(
$this->output,
count( $customFilesToBackup )
);
$this->output->writeln( "Adding 'custom-paths' ..." );
foreach( $customFilesToBackup as $customFile ) {
$localPath = preg_replace( '#^' . preg_quote( $this->mediawikiRoot ) . '#', '', $customFile );
$this->zip->addFile( $customFile, "filesystem/$localPath" );
$progressBar->advance();
}
$progressBar->finish();
$this->output->write( "\n" );
return $customFilesToBackup;
}

protected $tmpDumpFilepath = '';
Expand Down
67 changes: 64 additions & 3 deletions src/Commands/WikiRestore.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace MWStake\MediaWiki\CliAdm\Commands;

use DateTime;
use MWStake\MediaWiki\CliAdm\FarmInstanceSettingsManager;
use RecursiveIteratorIterator;
use RecursiveDirectoryIterator;
use Symfony\Component\Console\Command\Command;
Expand Down Expand Up @@ -80,6 +81,23 @@ class WikiRestore extends Command {
*/
protected $filesystem = null;

/**
* @var bool
*/
private $isFarmContext = false;

/**
*
* @var FarmInstanceSettingsManager
*/
private $farmSettingsManager = null;

/**
*
* @var string
*/
private $instanceName = '';

/**
*
* @var array
Expand Down Expand Up @@ -139,6 +157,9 @@ protected function execute( Input\InputInterface $input, OutputInterface $output
$this->readWikiConfig();
$this->importFilesystem();
$this->importDatabase();
if ( $this->isFarmContext ) {
$this->farmSettingsManager->setInstanceSetting( $this->instanceName, 'sfi_status', 'ready' );
}
$this->removeTempWorkDir();
$this->outputEndInfo( $output );
}
Expand Down Expand Up @@ -256,6 +277,49 @@ private function readWikiConfig() {
$this->settings = $settingsReader->getSettingsFromDirectory(
"{$this->tmpWorkingDir}/filesystem"
);
$this->setupFarmEnvironment();
}

/**
* @return void
* @throws Exception
*/
private function setupFarmEnvironment() {
$instanceSettingsFile = "$this->tmpWorkingDir/filesystem/settings.json";
if ( !file_exists( $instanceSettingsFile ) ) {
return;
}
$mainDbConnectionOptions = $this->profile->getDBImportOptions()['connection'] ?? null;
if ( !$mainDbConnectionOptions ) {
throw new Exception(
"No main database connection options found in profile, but it's required when in farm context"
);
}
$host = $mainDbConnectionOptions['dbserver'] ?? 'localhost';
$mainPdo = new PDO(
"mysql:host=$host;dbname={$mainDbConnectionOptions['dbname']}",
$mainDbConnectionOptions['dbuser'],
$mainDbConnectionOptions['dbpassword']
);
$settingsTable = ( $mainDbConnectionOptions['dbprefix' ] ?? '' ) . 'simple_farmer_instances';
$this->farmSettingsManager = new FarmInstanceSettingsManager( $mainPdo, $settingsTable );
$settings = $this->farmSettingsManager->getSettingsFromFile( $instanceSettingsFile );
if ( !$settings ) {
throw new Exception( "Failed to read settings.json for farm instance" );
}
$farmOptions = $this->profile->getOptions()['farm-options'] ?? null;
$instancesDir = $farmOptions['instances-dir'] ?? $this->mediawikiRoot . '/_sf_instances';
$this->instanceName = $settings['path'];
$this->settings['dbname'] = $settings['dbname'];
$this->settings['dbserver'] = $mainDbConnectionOptions['dbserver'];
$this->settings['dbuser'] = $mainDbConnectionOptions['dbuser'];
$this->settings['dbpassword'] = $mainDbConnectionOptions['dbpassword'];
$this->settings['dbprefix'] = $settings['dbprefix'];
$this->settings['wikiName'] = $settings['displayName'];
$this->mediawikiRoot = $instancesDir . '/' . $settings['path'];
if ( !$this->farmSettingsManager->assertInstanceEntryInitializedFromFile( $this->instanceName, $instanceSettingsFile ) ) {
throw new Exception( "Failed to init farm entry" );
}
}

/**
Expand All @@ -270,9 +334,6 @@ private function makePDO() {
'dbpassword' => $this->settings['dbpassword']
];

$profileDBOptions = $this->profile->getDBImportOptions();
$connection = $profileDBOptions['connection'] + $connection;

$dsn = "mysql:host={$connection['dbserver']}";
$pdo = new PDO( $dsn, $connection['dbuser'], $connection['dbpassword'] );
$pdo->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
Expand Down
5 changes: 3 additions & 2 deletions src/DatabaseImporter.php
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,9 @@ private function doImport( $pathname ) {
try {
$this->pdo->beginTransaction();
$this->pdo->exec( $tmpLine );
$this->pdo->commit();
if ($this->pdo->inTransaction() ) {
$this->pdo->commit();
}
} catch (PDOException $e) {
$this->output->writeln(
"<error>Error performing Query: " . $tmpLine . ": "
Expand All @@ -114,7 +116,6 @@ private function doImport( $pathname ) {
if ($errorDetect) {
return false;
}

}

private function isCommentLine( $line ) {
Expand Down
Loading

0 comments on commit fa61fac

Please sign in to comment.