From 1ff2bc4f3c694b7c76df8e57056c51ca39a23a34 Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Tue, 20 Jan 2015 18:07:24 +0100 Subject: config module structure completed. Many other fixes. Hidden pw field support. --- inc/configmodule.inc.php | 419 +++++++++++++++++++++++++++++++++++ inc/configmodule/adauth.inc.php | 130 +++-------- inc/configmodule/branding.inc.php | 58 ++--- inc/configmodule/customodule.inc.php | 37 +++- inc/configmodules.inc.php | 94 -------- inc/database.inc.php | 6 +- inc/property.inc.php | 24 +- inc/render.inc.php | 5 +- inc/taskmanager.inc.php | 4 +- inc/taskmanagercallback.inc.php | 49 +++- inc/trigger.inc.php | 5 + inc/util.inc.php | 17 ++ 12 files changed, 610 insertions(+), 238 deletions(-) create mode 100644 inc/configmodule.inc.php delete mode 100644 inc/configmodules.inc.php (limited to 'inc') diff --git a/inc/configmodule.inc.php b/inc/configmodule.inc.php new file mode 100644 index 00000000..5eff92ba --- /dev/null +++ b/inc/configmodule.inc.php @@ -0,0 +1,419 @@ + $title, + 'description' => $description, + 'group' => $group, + 'unique' => $unique, + 'sortOrder' => $sortOrder, + 'moduleClass' => $moduleClass, + 'wizardClass' => $wizardClass + ); + } + + /** + * Get fresh instance of ConfigModule subclass for given module type. + * + * @param string $moduleType name of module type + * @return \ConfigModule module instance + */ + public static function getInstance($moduleType) + { + self::loadDb(); + if (!isset(self::$moduleTypes[$moduleType])) + return false; + return new self::$moduleTypes[$moduleType]['moduleClass']; + } + + /** + * Get module instance from id. + * + * @param int $moduleId module id to get + * @return ConfigModule The requested module from DB, or false on error + */ + public static function get($moduleId) + { + $ret = Database::queryFirst("SELECT title, moduletype, filepath, contents, version FROM configtgz_module " + . " WHERE moduleid = :moduleid LIMIT 1", array('moduleid' => $moduleId)); + if ($ret === false) + return false; + $instance = self::getInstance($ret['moduletype']); + if ($instance === false) + return false; + $instance->currentVersion = $ret['version']; + $instance->moduleArchive = $ret['filepath']; + $instance->moduleData = json_decode($ret['contents'], true); + $instance->moduleId = $moduleId; + $instance->moduleTitle = $ret['title']; + return $instance; + } + + /** + * Get the module version. + * + * @return int module version + */ + protected abstract function moduleVersion(); + + /** + * Validate the module's configuration. + * + * @return boolean ok or not + */ + protected abstract function validateConfig(); + + /** + * Set module specific data. + * + * @param string $key key, name or id of data being set + * @param mixed $value Module specific data + * @return boolean true if data was successfully set, false otherwise (i.e. invalid data being set) + */ + public abstract function setData($key, $value); + + /** + * Module specific version of generate. + * + * @param string $tgz File name of tgz module to write final output to + * @return array|boolean true if generation is completed immediately, + * a task struct if some task needs to be run for generation, + * false on error + */ + protected abstract function generateInternal($tgz, $parent); + + private final function createFileName() + { + return CONFIG_TGZ_LIST_DIR . '/modules/' + . $this->moduleType() . '_id-' . $this->moduleId . '__' . mt_rand() . '-' . time() . '.tgz'; + } + + /** + * Get module id (in db) + * + * @return int id + */ + public final function id() + { + return $this->moduleId; + } + + /** + * Get module archive file name. + * + * @return string tgz file absolute path + */ + public final function archive() + { + return $this->moduleArchive; + } + + /** + * Get the module type. + * + * @return string module type + */ + public final function moduleType() + { + $name = get_class($this); + if ($name === false) + Util::traceError('ConfigModule::moduleType: get_class($this) returned false!'); + // ConfigModule_* + if (!preg_match('/^ConfigModule_(\w+)$/', $name, $out)) + Util::traceError('ConfigModule::moduleType: get_class($this) returned "' . $name . '"'); + return $out[1]; + } + + /** + * Insert this config module into DB. Only + * valid if the object was created using the creating constructor, + * not if the instance was created using a database entry (static get method). + * + * @param string $title display name of the module + * @return boolean true if inserted successfully, false if module config is invalid + */ + public final function insert($title) + { + if ($this->moduleId !== 0) + Util::traceError('ConfigModule::insert called when moduleId != 0'); + if (!$this->validateConfig()) + return false; + $this->moduleTitle = $title; + // Insert + Database::exec("INSERT INTO configtgz_module (title, moduletype, filepath, contents, version, status) " + . " VALUES (:title, :type, '', :contents, :version, :status)", array( + 'title' => $title, + 'type' => $this->moduleType(), + 'contents' => json_encode($this->moduleData), + 'version' => 0, + 'status' => 'MISSING' + )); + $this->moduleId = Database::lastInsertId(); + if (!is_numeric($this->moduleId)) + Util::traceError('Inserting new config module into DB did not yield a numeric insert id'); + $this->moduleArchive = $this->createFileName(); + Database::exec("UPDATE configtgz_module SET filepath = :path WHERE moduleid = :moduleid LIMIT 1", array( + 'path' => $this->moduleArchive, + 'moduleid' => $this->moduleId + )); + return true; + } + + /** + * Generate the module's tgz, don't wait for completion. + * Updating the database etc. will happen later through a callback. + * + * @param boolean $deleteOnError if true, the db entry will be deleted if generation failed + * @param int $timeoutMs maximum time in milliseconds we wait for completion + * @return string|boolean task id if deferred generation was started, + * true if generation succeeded (without using a task or within $timeoutMs) + * false on error + */ + public final function generate($deleteOnError, $parent = NULL, $timeoutMs = 0) + { + if ($this->moduleId === 0 || $this->moduleTitle === false) + Util::traceError('ConfigModule::generateAsync called on uninitialized/uninserted module!'); + $tmpTgz = '/tmp/bwlp-id-' . $this->moduleId . '_' . mt_rand() . '_' . time() . '.tgz'; + $ret = $this->generateInternal($tmpTgz, $parent); + // Wait for generation if requested + if ($timeoutMs > 0 && isset($ret['id']) && !Taskmanager::isFinished($ret)) + $ret = Taskmanager::waitComplete($ret, $timeoutMs); + if ($ret === true || (isset($ret['statusCode']) && $ret['statusCode'] === TASK_FINISHED)) + return $this->markUpdated($tmpTgz); // Finished + if (!is_array($ret) || !isset($ret['id']) || Taskmanager::isFailed($ret)) { + if (is_array($ret)) // Failed + Taskmanager::addErrorMessage($ret); + if ($deleteOnError) + $this->delete(); + else + $this->markFailed(); + return false; + } + // Still running, add callback + TaskmanagerCallback::addCallback($ret, 'cbConfModCreated', array( + 'moduleid' => $this->moduleId, + 'deleteOnError' => $deleteOnError, + 'tmpTgz' => $tmpTgz + )); + return $ret['id']; + } + + /** + * Delete the module. + */ + public final function delete() + { + if ($this->moduleId === 0) + Util::traceError('ConfigModule::delete called with invalid module id!'); + $ret = Database::exec("DELETE FROM configtgz_module WHERE moduleid = :moduleid LIMIT 1", array( + 'moduleid' => $id + )) !== false; + if ($ret !== false) { + if ($this->moduleArchive) + Taskmanager::submit('DeleteFile', array('file' => $this->moduleArchive), true); + $this->moduleId = 0; + $this->moduleData = false; + $this->moduleTitle = false; + $this->moduleArchive = false; + } + } + + private final function markUpdated($tmpTgz) + { + if ($this->moduleId === 0) + Util::traceError('ConfigModule::markUpdated called with invalid module id!'); + if ($this->moduleArchive === false) + $this->moduleArchive = $this->createFileName(); + // Move file + if ($tmpTgz === false) { + if (!file_exists($this->moduleArchive)) { + EventLog::failure('ConfigModule::markUpdated for "' . $this->moduleTitle . '" called with no tmpTgz and no existing tgz!'); + $this->markFailed(); + return false; + } + } else { + $task = Taskmanager::submit('MoveFile', array( + 'source' => $tmpTgz, + 'destination' => $this->moduleArchive + )); + $task = Taskmanager::waitComplete($task, 5000); + if (Taskmanager::isFailed($task) || !Taskmanager::isFinished($task)) { + if (!API && !AJAX) + Taskmanager::addErrorMessage($task); + else + EventLog::failure('Could not move ' . $tmpTgz . ' to ' . $this->moduleArchive . ' while generating "' . $this->moduleTitle . '"'); + $this->markFailed(); + return false; + } + } + // Update DB entry + return Database::exec("UPDATE configtgz_module SET filepath = :filename, version = :version, status = 'OK' WHERE moduleid = :id LIMIT 1", array( + 'id' => $this->moduleId, + 'filename' => $this->moduleArchive, + 'version' => $this->moduleVersion() + )) !== false; + } + + private final function markFailed() + { + if ($this->moduleId === 0) + Util::traceError('ConfigModule::markFailed called with invalid module id!'); + if ($this->moduleArchive === false) + $this->moduleArchive = $this->createFileName(); + if (!file_exists($this->moduleArchive)) + $status = 'MISSING'; + else + $status = 'OUTDATED'; + return Database::exec("UPDATE configtgz_module SET filepath = :filename, status = :status WHERE moduleid = :id LIMIT 1", array( + 'id' => $this->moduleId, + 'filename' => $this->moduleArchive, + 'status' => $status + )) !== false; + } + + ################# Callbacks ############## + + /** + * Event callback for when the server ip changed. + * Override this if you need to handle this, otherwise + * the base implementation does nothing. + */ + public function event_serverIpChanged() + { + // Do::Nothing() + } + + ##################### STATIC CALLBACKS ##################### + + /** + * Will be called if the server's IP address changes. The event will be propagated + * to all config module classes so action can be taken if appropriate. + */ + public static function serverIpChanged() + { + self::loadDb(); + foreach (self::$moduleTypes as $module) { + $instance = new $module['moduleClass']; + $instance->event_serverIpChanged(); + } + } + + /** + * Called when (re)generating a config module failed, so we can + * update the status in the DB and add a server log entry. + * + * @param array $task + * @param array $args contains 'moduleid' and optionally 'deleteOnError' and 'tmpTgz' + */ + public static function generateFailed($task, $args) + { + if (!isset($args['moduleid']) || !is_numeric($args['moduleid'])) { + EventLog::warning('Ignoring generateFailed event as it has no moduleid assigned.'); + return; + } + $module = self::get($args['moduleid']); + if ($module === false) { + EventLog::warning('generateFailed callback for module id ' . $args['moduleid'] . ', but no instance could be generated.'); + return; + } + if (isset($task['data']['error'])) + $error = $task['data']['error']; + elseif (isset($task['data']['messages'])) + $error = $task['data']['messages']; + else + $error = ''; + EventLog::failure("Generating module '" . $module->moduleTitle . "' failed.", $error); + if ($args['deleteOnError']) + $module->delete(); + else + $module->markFailed(); + } + + /** + * (Re)generating a config module succeeded. Update db entry. + * + * @param array $args contains 'moduleid' and optionally 'deleteOnError' and 'tmpTgz' + */ + public static function generateSucceeded($args) + { + if (!isset($args['moduleid']) || !is_numeric($args['moduleid'])) { + EventLog::warning('Ignoring generateSucceeded event as it has no moduleid assigned.'); + return; + } + $module = self::get($args['moduleid']); + if ($module === false) { + EventLog::warning('generateSucceeded callback for module id ' . $args['moduleid'] . ', but no instance could be generated.'); + return; + } + if (isset($args['tmpTgz'])) + $module->markUpdated($args['tmpTgz']); + else + $module->markUpdated(false); + } + +} diff --git a/inc/configmodule/adauth.inc.php b/inc/configmodule/adauth.inc.php index c0d4860c..06ac5460 100644 --- a/inc/configmodule/adauth.inc.php +++ b/inc/configmodule/adauth.inc.php @@ -1,7 +1,7 @@ $title, 'modid' => self::MODID)); - $id = Database::lastInsertId(); - if (!is_numeric($id)) Util::traceError('Inserting new AD config to DB did not yield a numeric insert id'); - // Entry created, now try to get a free port for the proxy - $res = Database::simpleQuery("SELECT moduleid, contents FROM configtgz_module WHERE moduletype = :modid", array( - 'modid' => self::MODID - )); - $ports = array(); - while ($row = $res->fetch(PDO::FETCH_ASSOC)) { - if ($row['moduleid'] == $id) { - // ... - } else { - $data = json_decode($row['contents'], true); - if (isset($data['proxyport'])) $ports[] = $data['proxyport']; - } - } - $port = 3300; - while (in_array($port, $ports)) { - $port++; - } - // Port determined, carry on... - $ownEntry = array( - 'server' => $server, - 'searchbase' => $searchbase, - 'binddn' => $binddn, - 'bindpw' => $bindpw, - 'home' => $home, - 'proxyport' => $port - ); - $data = json_encode($ownEntry); - if ($data === false) Util::traceError('Serializing the AD data failed.'); - $moduleTgz = CONFIG_TGZ_LIST_DIR . '/modules/AD_AUTH_id_' . $id . '.' . mt_rand() . '.tgz'; - Database::exec("UPDATE configtgz_module SET filepath = :filename, contents = :contents WHERE moduleid = :id LIMIT 1", array( - 'id' => $id, - 'filename' => $moduleTgz, - 'contents' => $data - )); - Database::exec("UNLOCK TABLES"); - // Add archive file name to array before returning it - $ownEntry['moduleid'] = $id; - $ownEntry['filename'] = $moduleTgz; - return $ownEntry; + $config = $this->moduleData; + $config['parentTask'] = $parent; + $config['failOnParentFail'] = false; + $config['proxyip'] = Property::getServerIp(); + $config['proxyport'] = 3100 + $this->id(); + $config['filename'] = $tgz; + $config['moduleid'] = $this->id(); + return Taskmanager::submit('CreateAdConfig', $config); } - /** - * To be called if the server ip changes, as it's embedded in the AD module configs. - * This will then recreate all AD tgz modules. - */ - private static function rebuildAll($parent = NULL) + protected function moduleVersion() { - // Stop all running instances of ldadp - $task = Taskmanager::submit('LdadpLauncher', array( - 'parentTask' => $parent, - 'failOnParentFail' => false, - 'ids' => array() - )); - $ads = self::getAll(); - if (empty($ads)) // Nothing to do - return false; + return self::VERSION; + } - if (isset($task['id'])) - $parent = $task['id']; - foreach ($ads as $ad) { - $ad['parentTask'] = $parent; - $ad['failOnParentFail'] = false; - $ad['proxyip'] = Property::getServerIp(); - $task = Taskmanager::submit('CreateAdConfig', $ad); - if (isset($task['id'])) - $parent = $task['id']; - } - Trigger::ldadp($parent); - return $parent; + protected function validateConfig() + { + // Check if required fields are filled + return Util::hasAllKeys($this->moduleData, self::$REQUIRED_FIELDS); } - - /** - * Get all existing AD proxy configs. - * - * @return array array of ad configs in DB with fields: - * moduleid, filename, server, searchbase, binddn, bindpw, home, proxyport - */ - public static function getAll() + + public function setData($key, $value) { - $res = Database::simpleQuery("SELECT moduleid, filepath, contents FROM configtgz_module WHERE moduletype = :modid", array( - 'modid' => self::MODID - )); - $mods = array(); - while ($row = $res->fetch(PDO::FETCH_ASSOC)) { - $data = json_decode($row['contents'], true); - $data['moduleid'] = $row['moduleid']; - $data['filename'] = $row['filepath']; - $mods[] = $data; - } - return $mods; + if (!in_array($key, self::$REQUIRED_FIELDS) && !in_array($key, self::$OPTIONAL_FIELDS)) + return false; + $this->moduleData[$key] = $value; + return true; } - + // ############## Callbacks ############################# - + /** * Server IP changed - rebuild all AD modules. */ public function event_serverIpChanged() { - self::rebuildAll(); + $this->generate(false); } - + } diff --git a/inc/configmodule/branding.inc.php b/inc/configmodule/branding.inc.php index f293fda6..e8bd1da7 100644 --- a/inc/configmodule/branding.inc.php +++ b/inc/configmodule/branding.inc.php @@ -1,6 +1,6 @@ $title, 'modid' => self::MODID)); - $id = Database::lastInsertId(); - if (!is_numeric($id)) - Util::traceError('Inserting new Branding Module into DB did not yield a numeric insert id'); - // Move tgz - $moduleTgz = CONFIG_TGZ_LIST_DIR . '/modules/BRANDING_id_' . $id . '.' . mt_rand() . '.tgz'; - $task = Taskmanager::submit('MoveFile', array( - 'source' => $archive, - 'destination' => $moduleTgz - )); - $task = Taskmanager::waitComplete($task, 3000); - if (Taskmanager::isFailed($task) || $task['statusCode'] !== TASK_FINISHED) { - Taskmanager::addErrorMessage($task); - Database::exec("DELETE FROM configtgz_module WHERE moduleid = :moduleid LIMIT 1", array( - 'moduleid' => $id - )); + if (!$this->validateConfig()) { + if ($this->archive() !== false && file_exists($this->archive())) + return true; // No new temp file given, old archive still exists, pretend it worked... return false; } - // Update with path - Database::exec("UPDATE configtgz_module SET filepath = :filename WHERE moduleid = :id LIMIT 1", array( - 'id' => $id, - 'filename' => $moduleTgz + $task = Taskmanager::submit('MoveFile', array( + 'source' => $this->tmpFile, + 'destination' => $tgz, + 'parentTask' => $parent, + 'failOnParentFail' => false )); - return true; + return $task; } - + + protected function moduleVersion() + { + return self::VERSION; + } + + protected function validateConfig() + { + return $this->tmpFile !== false && file_exists($this->tmpFile); + } + + public function setData($key, $value) + { + if ($key !== 'tmpFile' || !file_exists($value)) + return false; + $this->tmpFile = $value; + } + } diff --git a/inc/configmodule/customodule.inc.php b/inc/configmodule/customodule.inc.php index 89f0aca6..195f738f 100644 --- a/inc/configmodule/customodule.inc.php +++ b/inc/configmodule/customodule.inc.php @@ -1,6 +1,6 @@ validateConfig()) { + if ($this->archive() !== false && file_exists($this->archive())) + return true; // No new temp file given, old archive still exists, pretend it worked... + return false; + } + $task = Taskmanager::submit('MoveFile', array( + 'source' => $this->tmpFile, + 'destination' => $tgz, + 'parentTask' => $parent, + 'failOnParentFail' => false + )); + return $task; + } + + protected function moduleVersion() + { + return self::VERSION; + } + + protected function validateConfig() + { + return $this->tmpFile !== false && file_exists($this->tmpFile); + } + + public function setData($key, $value) + { + if ($key !== 'tmpFile' || !file_exists($value)) + return false; + $this->tmpFile = $value; + } } diff --git a/inc/configmodules.inc.php b/inc/configmodules.inc.php deleted file mode 100644 index bf870e4f..00000000 --- a/inc/configmodules.inc.php +++ /dev/null @@ -1,94 +0,0 @@ - $title, - 'description' => $description, - 'group' => $group, - 'unique' => $unique, - 'sortOrder' => $sortOrder, - 'moduleClass' => $moduleClass, - 'wizardClass' => $wizardClass - ); - } - - /** - * Will be called if the server's IP address changes. The event will be propagated - * to all config module classes so action can be taken if appropriate. - */ - public static function serverIpChanged() - { - self::loadDb(); - foreach (self::$moduleTypes as $module) { - $instance = new $module['moduleClass']; - $instance->event_serverIpChanged(); - } - } - -} - -/** - * Base class for config modules - */ -abstract class ConfigModule -{ - - public function event_serverIpChanged() - { - - } - -} diff --git a/inc/database.inc.php b/inc/database.inc.php index 2c535d04..efc330fe 100644 --- a/inc/database.inc.php +++ b/inc/database.inc.php @@ -20,7 +20,7 @@ class Database */ public static function getExpectedSchemaVersion() { - return 8; + return 9; } public static function needSchemaUpdate() @@ -47,7 +47,8 @@ class Database /** * If you just need the first row of a query you can use this. - * Will return an associative array, or false if no row matches the query + * + * @return array|boolean Associative array representing row, or false if no row matches the query */ public static function queryFirst($query, $args = array(), $ignoreError = false) { @@ -64,6 +65,7 @@ class Database * * @param string $query Query to run * @param array $args Arguments to query + * @param boolean $ignoreError Ignore query errors and just return false * @return int|boolean Number of rows affected, or false on error */ public static function exec($query, $args = array(), $ignoreError = false) diff --git a/inc/property.inc.php b/inc/property.inc.php index c16c8ad7..7c3b7d37 100644 --- a/inc/property.inc.php +++ b/inc/property.inc.php @@ -41,12 +41,14 @@ class Property */ private static function set($key, $value, $minage = 0) { - Database::exec("INSERT INTO property (name, value, dateline) VALUES (:key, :value, :dateline)" - . " ON DUPLICATE KEY UPDATE value = VALUES(value), dateline = VALUES(dateline)", array( - 'key' => $key, - 'value' => $value, - 'dateline' => ($minage === 0 ? 0 : time() + ($minage * 60)) - )); + if (self::$cache === false || self::get($key) != $value) { // Simple compare, so it works for numbers accidentally casted to string somewhere + Database::exec("INSERT INTO property (name, value, dateline) VALUES (:key, :value, :dateline)" + . " ON DUPLICATE KEY UPDATE value = VALUES(value), dateline = VALUES(dateline)", array( + 'key' => $key, + 'value' => $value, + 'dateline' => ($minage === 0 ? 0 : time() + ($minage * 60)) + )); + } if (self::$cache !== false) { self::$cache[$key] = $value; } @@ -181,5 +183,15 @@ class Property { return self::get('password-type', 'password'); } + + public static function setNeedsCallback($value) + { + return self::set('need-callback', $value, 5); + } + + public static function getNeedsCallback() + { + return self::get('need-callback', 0); + } } diff --git a/inc/render.inc.php b/inc/render.inc.php index 5827dcf9..31ba5a7d 100644 --- a/inc/render.inc.php +++ b/inc/render.inc.php @@ -69,7 +69,8 @@ class Render ', self::$footer , - '' + ' + ' ; if ($zip) { Header('Content-Encoding: gzip'); @@ -98,7 +99,7 @@ class Render } /** - * Add raw html data to the footer-section of the generated page (after the closing body tag) + * Add raw html data to the footer-section of the generated page (right before the closing body tag) */ public static function addFooter($html) { diff --git a/inc/taskmanager.inc.php b/inc/taskmanager.inc.php index 05db5b5d..5045fc75 100644 --- a/inc/taskmanager.inc.php +++ b/inc/taskmanager.inc.php @@ -29,7 +29,7 @@ class Taskmanager * @param array $data data to pass to the task. the structure depends on the task. * @param boolean $async if true, the function will not wait for the reply of the taskmanager, which means * the return value is just true (and you won't know if the task could acutally be started) - * @return array struct representing the task status, or result of submit, false on communication error + * @return array struct representing the task status (as a result of submit); false on communication error */ public static function submit($task, $data = false, $async = false) { @@ -154,7 +154,7 @@ class Taskmanager { if (!is_array($task) || !isset($task['statusCode']) || !isset($task['id'])) return false; - if ($task['statusCode'] === TASK_ERROR || $task['statusCode'] === TASK_FINISHED) + if ($task['statusCode'] !== TASK_WAITING && $task['statusCode'] !== TASK_PROCESSING) return true; return false; } diff --git a/inc/taskmanagercallback.inc.php b/inc/taskmanagercallback.inc.php index 951a1de3..a42f4819 100644 --- a/inc/taskmanagercallback.inc.php +++ b/inc/taskmanagercallback.inc.php @@ -13,7 +13,7 @@ class TaskmanagerCallback * @param string|array $task Task or Task ID to define callback for * @param string $callback name of callback function, must be a static method in this class */ - public static function addCallback($task, $callback) + public static function addCallback($task, $callback, $args = NULL) { if (!call_user_func_array('method_exists', array('TaskmanagerCallback', $callback))) { EventLog::warning("addCallback: Invalid callback function: $callback"); @@ -25,10 +25,16 @@ class TaskmanagerCallback EventLog::warning("addCallback: Not a valid task id: $task"); return; } - Database::exec("INSERT INTO callback (taskid, dateline, cbfunction) VALUES (:task, UNIX_TIMESTAMP(), :callback)", array( + if (is_null($args)) + $args = ''; + else + $args = serialize($args); + Database::exec("INSERT INTO callback (taskid, dateline, cbfunction, args) VALUES (:task, UNIX_TIMESTAMP(), :callback, :args)", array( 'task' => $task, - 'callback' => $callback + 'callback' => $callback, + 'args' => $args )); + Property::setNeedsCallback(1); } /** @@ -39,7 +45,7 @@ class TaskmanagerCallback public static function getPendingCallbacks() { $retval = array(); - $res = Database::simpleQuery("SELECT taskid, cbfunction FROM callback"); + $res = Database::simpleQuery("SELECT taskid, cbfunction, args FROM callback"); while ($row = $res->fetch(PDO::FETCH_ASSOC)) { $retval[$row['taskid']][] = $row; } @@ -50,7 +56,7 @@ class TaskmanagerCallback * Handle the given callback. Will delete the entry from the callback * table if appropriate. * - * @param array $callback entry from the callback table (cbfunction + taskid) + * @param array $callback entry from the callback table (cbfunction + taskid + args) * @param array $status status of the task as returned by the taskmanager. If NULL it will be queried. */ public static function handleCallback($callback, $status = NULL) @@ -69,7 +75,10 @@ class TaskmanagerCallback if (!call_user_func_array('method_exists', $func)) { Eventlog::warning("handleCallback: Callback {$callback['cbfunction']} doesn't exist."); } else { - call_user_func($func, $status); + if (empty($callback['args'])) + call_user_func($func, $status); + else + call_user_func($func, $status, unserialize($callback['args'])); } } } @@ -85,13 +94,35 @@ class TaskmanagerCallback EventLog::warning("Could not start/stop LDAP-AD-Proxy instances", $task['data']['messages']); } + /** + * Result of restoring the server configuration + */ public static function dbRestored($task) { - error_log("dbRestored."); - if (Taskmanager::isFinished($task) && !Taskmanager::isFailed($task)) { - error_log("LOGGING."); + if (!Taskmanager::isFailed($task)) { EventLog::info('Configuration backup restored.'); } } + + public static function adConfigCreate($task) + { + if (Taskmanager::isFailed($task)) + EventLog::warning("Could not generate Active Directory configuration", $task['data']['error']); + } + + /** + * Generating a config module has finished. + * + * @param array $task task obj + * @param array $args has keys 'moduleid' and optionally 'deleteOnError' and 'tmpTgz' + */ + public static function cbConfModCreated($task, $args) + { + if (Taskmanager::isFailed($task)) { + ConfigModule::generateFailed($task, $args); + } else { + ConfigModule::generateSucceeded($args); + } + } } diff --git a/inc/trigger.inc.php b/inc/trigger.inc.php index 38b25540..0410b2be 100644 --- a/inc/trigger.inc.php +++ b/inc/trigger.inc.php @@ -136,6 +136,7 @@ class Trigger */ public static function checkCallbacks() { + $tasksLeft = false; $callbackList = TaskmanagerCallback::getPendingCallbacks(); foreach ($callbackList as $taskid => $callbacks) { $status = Taskmanager::status($taskid); @@ -146,7 +147,11 @@ class Trigger } if (Taskmanager::isFailed($status) || Taskmanager::isFinished($status)) Taskmanager::release($status); + else + $tasksLeft = true; } + if (!$tasksLeft) + Property::setNeedsCallback(0); } private static function triggerDaemons($action, $parent, &$taskids) diff --git a/inc/util.inc.php b/inc/util.inc.php index cce46536..9d0eced9 100644 --- a/inc/util.inc.php +++ b/inc/util.inc.php @@ -216,4 +216,21 @@ SADFACE; return true; } + /** + * Check whether $arrax contains all keys given in $keyList + * @param array $array An array + * @param array $keyList A list of strings which must all be valid keys in $array + * @return boolean + */ + public static function hasAllKeys($array, $keyList) + { + if (!is_array($array)) + return false; + foreach ($keyList as $key) { + if (!isset($array[$key])) + return false; + } + return true; + } + } -- cgit v1.2.3-55-g7522