Skip to content
Snippets Groups Projects
Commit b02778cd authored by Jared Hancock's avatar Jared Hancock
Browse files

Support multiple update streams

This patch allows the upgrader to upgrade multiple database update
'streams'. The main stream is renamed to 'core' to reflect changes made
to the config class. This will significantly ease customizations
requiring database customizations, and will make plugins requiring
database changes possible.
parent bf1c4ce5
No related branches found
No related tags found
No related merge requests found
Showing
with 184 additions and 40 deletions
......@@ -25,7 +25,7 @@ class UpgraderAjaxAPI extends AjaxController {
if(!$thisstaff or !$thisstaff->isAdmin() or !$ost)
Http::response(403, 'Access Denied');
$upgrader = new Upgrader($ost->getDBSignature(), TABLE_PREFIX, SQL_DIR);
$upgrader = new Upgrader(TABLE_PREFIX, UPGRADE_DIR.'streams/');
//Just report the next action on the first call.
if(!$_SESSION['ost_upgrader'] || !$_SESSION['ost_upgrader'][$upgrader->getShash()]['progress']) {
......@@ -51,7 +51,7 @@ class UpgraderAjaxAPI extends AjaxController {
Http::response(200, "Upgraded to $version ... post-upgrade checks!");
exit;
}
} else {
} else {
//Abort: Upgrade pending but NOT upgradable - invalid or wrong hash.
$upgrader->abort(sprintf('Upgrade Failed: Invalid or wrong hash [%s]',$ost->getDBSignature()));
}
......
......@@ -70,6 +70,28 @@ class DatabaseMigrater {
return array_filter($patches);
}
/**
* Reads update stream information from UPGRADE_DIR/<stream>/<stream>.md5.
* Each declared stream folder should contain a file under the name of the
* stream with an 'md5' extension. The file will be the md5 of the
* signature of the tip of the stream. A corresponding config variable
* 'schema_signature' should exist in the namespace of the stream itself.
* If the md5 file doesn't match the schema_signature on record, then an
* update is triggered and the patches in the stream folder are used to
* upgrade the database.
*/
/* static */ function getUpgradeStreams($basedir) {
static $streams = array();
if ($streams) return $streams;
$h = opendir($basedir);
while (($next = readdir($h)) !== false)
if (file_exists($basedir."$next.md5") && is_dir($basedir.$next))
$streams[$next] =
trim(file_get_contents($basedir."$next.md5"));
return $streams;
}
}
......
......@@ -20,6 +20,7 @@
require_once(INCLUDE_DIR.'class.config.php'); //Config helper
require_once(INCLUDE_DIR.'class.csrf.php'); //CSRF token class.
require_once(INCLUDE_DIR.'class.migrater.php');
define('LOG_WARN',LOG_WARNING);
......@@ -60,8 +61,11 @@ class osTicket {
}
function isUpgradePending() {
return strcasecmp(SCHEMA_SIGNATURE,
$this->getConfig()->getSchemaSignature());
foreach (DatabaseMigrater::getUpgradeStreams(UPGRADE_DIR.'streams/') as $stream=>$hash)
if (strcasecmp($hash,
$this->getConfig()->getSchemaSignature($stream)))
return true;
return false;
}
function getSession() {
......@@ -72,8 +76,8 @@ class osTicket {
return $this->config;
}
function getDBSignature() {
return $this->getConfig()->getSchemaSignature();
function getDBSignature($namespace='core') {
return $this->getConfig()->getSchemaSignature($namespace);
}
function getVersion() {
......
......@@ -17,7 +17,128 @@
require_once INCLUDE_DIR.'class.setup.php';
require_once INCLUDE_DIR.'class.migrater.php';
class Upgrader extends SetupWizard {
class Upgrader {
function Upgrader($prefix, $basedir) {
global $ost;
$this->streams = array();
foreach (DatabaseMigrater::getUpgradeStreams($basedir) as $stream=>$hash) {
$signature = $ost->getConfig()->getSchemaSignature($stream);
$this->streams[$stream] = new StreamUpgrader($signature, $hash, $stream,
$prefix, $basedir.$stream.'/', $this);
}
//Init persistent state of upgrade.
$this->state = &$_SESSION['ost_upgrader']['state'];
$this->mode = &$_SESSION['ost_upgrader']['mode'];
$this->current = &$_SESSION['ost_upgrader']['stream'];
if (!$this->current || $this->getCurrentStream()->isFinished()) {
$streams = array_keys($this->streams);
do {
$this->current = array_shift($streams);
} while ($this->current && $this->getCurrentStream()->isFinished());
}
}
function getCurrentStream() {
return $this->streams[$this->current];
}
function isUpgradable() {
if ($this->isAborted())
return false;
foreach ($this->streams as $s)
if (!$s->isUpgradable())
return false;
return true;
}
function isAborted() {
return !strcasecmp($this->getState(), 'aborted');
}
function getState() {
return $this->state;
}
function setState($state) {
$this->state = $state;
}
function getMode() {
return $this->mode;
}
function setMode($mode) {
$this->mode = $mode;
}
function upgrade() {
if (!$this->current)
return true;
return $this->getCurrentStream()->upgrade();
}
function check_prereq() {
if ($this->getCurrentStream())
return $this->getCurrentStream()->check_prereq();
}
function check_php() {
if ($this->getCurrentStream())
return $this->getCurrentStream()->check_php();
}
function check_mysql() {
if ($this->getCurrentStream())
return $this->getCurrentStream()->check_mysql();
}
function getNumPendingTasks() {
return $this->getCurrentStream()->getNumPendingTasks();
}
function doTasks() {
if ($this->getNumPendingTasks())
return $this->getCurrentStream()->doTasks();
}
function getErrors() {
return $this->getCurrentStream()->getError();
}
function getNextAction() {
if ($this->getCurrentStream())
return $this->getCurrentStream()->getNextAction();
}
function getNextVersion() {
return $this->getCurrentStream()->getNextVersion();
}
function getSchemaSignature() {
if ($this->getCurrentStream())
return $this->getCurrentStream()->getSchemaSignature();
}
function getSHash() {
return $this->getCurrentStream()->getSHash();
}
}
/**
* Updates a single database stream. In the classical sense, osTicket only
* maintained a single database update stream. In that model, this
* represents upgrading that single stream. In multi-stream mode,
* customizations and plugins are supported to have their own respective
* database update streams. The Upgrader class is used to coordinate updates
* for all the streams, whereas the work to upgrade each stream is done in
* this class
*/
class StreamUpgrader extends SetupWizard {
var $prefix;
var $sqldir;
......@@ -26,22 +147,32 @@ class Upgrader extends SetupWizard {
var $state;
var $mode;
function Upgrader($signature, $prefix, $sqldir) {
$this->signature = $signature;
/**
* Parameters:
* schema_signature - (string<md5-hex>) Current database-reflected (via
* config table) version of the stream
* target - (stream<md5-hex>) Current stream tip, as reflected by
* streams/<stream>.md5
* stream - (string) Name of the stream (folder)
* prefix - (string) Database table prefix
* sqldir - (string<path>) Path of sql patches
* upgrader - (Upgrader) Parent coordinator of parallel stream updates
*/
function StreamUpgrader($schema_signature, $target, $stream, $prefix, $sqldir, $upgrader) {
$this->signature = $schema_signature;
$this->target = $target;
$this->prefix = $prefix;
$this->sqldir = $sqldir;
$this->errors = array();
$this->mode = 'ajax'; //
$this->upgrader = $upgrader;
$this->name = $stream;
//Disable time limit if - safe mode is set.
if(!ini_get('safe_mode'))
set_time_limit(0);
//Init persistent state of upgrade.
$this->state = &$_SESSION['ost_upgrader']['state'];
$this->mode = &$_SESSION['ost_upgrader']['mode'];
//Init the task Manager.
if(!isset($_SESSION['ost_upgrader'][$this->getShash()]))
......@@ -57,9 +188,10 @@ class Upgrader extends SetupWizard {
function onError($error) {
global $ost, $thisstaff;
$ost->logError('Upgrader Error', $error);
$subject = '['.$this->name.']: Upgrader Error';
$ost->logError($subject, $error);
$this->setError($error);
$this->setState('aborted');
$this->upgrader->setState('aborted');
//Alert staff upgrading the system - if the email is not same as admin's
// admin gets alerted on error log (above)
......@@ -70,7 +202,6 @@ class Upgrader extends SetupWizard {
if(!($email=$ost->getConfig()->getAlertEmail()))
$email=$ost->getConfig()->getDefaultEmail(); //will take the default email.
$subject = 'Upgrader Error';
if($email) {
$email->sendAlert($thisstaff->getEmail(), $subject, $error);
} else {//no luck - try the system mail.
......@@ -80,11 +211,7 @@ class Upgrader extends SetupWizard {
}
function isUpgradable() {
return (!$this->isAborted() && $this->getNextPatch());
}
function isAborted() {
return !strcasecmp($this->getState(), 'aborted');
return $this->getNextPatch();
}
function getSchemaSignature() {
......@@ -103,25 +230,9 @@ class Upgrader extends SetupWizard {
return $this->sqldir;
}
function getState() {
return $this->state;
}
function setState($state) {
$this->state = $state;
}
function getMode() {
return $this->mode;
}
function setMode($mode) {
$this->mode = $mode;
}
function getMigrater() {
if(!$this->migrater)
$this->migrater = new DatabaseMigrater($this->signature, SCHEMA_SIGNATURE, $this->sqldir);
$this->migrater = new DatabaseMigrater($this->signature, $this->target, $this->sqldir);
return $this->migrater;
}
......@@ -146,6 +257,12 @@ class Upgrader extends SetupWizard {
return $info['version'];
}
function isFinished() {
# TODO: 1. Check if current and target hashes match,
# 2. Any pending tasks
return !($this->getNextPatch() || $this->getPendingTasks());
}
function readPatchInfo($patch) {
$info = array();
if (preg_match(':/\*\*(.*)\*/:s', file_get_contents($patch), $matches)) {
......@@ -170,7 +287,7 @@ class Upgrader extends SetupWizard {
$action = "Upgrade to $nextversion";
}
return $action;
return '['.$this->name.'] '.$action;
}
function getNumPendingTasks() {
......
740428f9986da6ad85f88ec841b57bfe
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment