diff --git a/config/modules/monitoring/backends.ini b/config/modules/monitoring/backends.ini index 9a26e5515..a3296d8d9 100644 --- a/config/modules/monitoring/backends.ini +++ b/config/modules/monitoring/backends.ini @@ -1,10 +1,6 @@ [localdb] type = ido -host = localhost -port = 3306 -user = "icinga" -pass = "icinga" -db = "icinga" +resource = "ido-mysql" disabled = 0 [locallive] diff --git a/config/resources.ini b/config/resources.ini index b45ef51e6..2b4a80f4a 100644 --- a/config/resources.ini +++ b/config/resources.ini @@ -14,7 +14,7 @@ [icingaweb-pgsql] -type = db +type = db db = pgsql ; PostgreSQL host = localhost password = icinga @@ -29,10 +29,20 @@ password = icinga username = icingaweb dbname = icingaweb -[ido] -type = db -dbname = mysql -host = localhost -password = icinga -username = icingaweb -db = icingaweb \ No newline at end of file +[ido-pgsql] +type = db +db = pgsql ; PostgreSQL +host = localhost +password = icinga +username = icinga +port = 5432 +dbname = icinga + +[ido-mysql] +type = db +db = mysql ; MySQL +host = localhost +password = icinga +username = icinga +port = 3306 +dbname = icinga \ No newline at end of file diff --git a/library/Icinga/Application/DbAdapterFactory.php b/library/Icinga/Application/DbAdapterFactory.php index 0b98a3a0b..343367439 100644 --- a/library/Icinga/Application/DbAdapterFactory.php +++ b/library/Icinga/Application/DbAdapterFactory.php @@ -28,13 +28,14 @@ namespace Icinga\Application; -use Zend_Config; -use Zend_Db; -use Icinga\Application\Logger; -use Icinga\Util\ConfigAwareFactory; -use Icinga\Exception\ConfigurationError; -use Icinga\Exception\ProgrammingError; -use Tests\Icinga\Application\ZendDbMock; +use \PDO; +use \Zend_Config; +use \Zend_Db; +use \Zend_Db_Adapter_Abstract; +use \Icinga\Application\Logger; +use \Icinga\Util\ConfigAwareFactory; +use \Icinga\Exception\ConfigurationError; +use \Icinga\Exception\ProgrammingError; /** * Create resources using short identifiers referring to configuration entries @@ -62,6 +63,29 @@ class DbAdapterFactory implements ConfigAwareFactory */ private static $resourceCache = array(); + /** + * Array of PDO driver options + * + * @see http://www.php.net/manual/en/pdo.constants.php + * @var array + */ + private static $defaultPdoDriverOptions = array( + PDO::ATTR_TIMEOUT => 2, + PDO::ATTR_CASE => PDO::CASE_LOWER + ); + + /** + * Array of Zend_Db adapter options + * + * @see http://framework.zend.com/manual/1.12/en/zend.db.html + * @var array + */ + private static $defaultZendDbAdapterOptions = array( + Zend_Db::AUTO_QUOTE_IDENTIFIERS => false, + Zend_Db::CASE_FOLDING => Zend_Db::CASE_LOWER, + Zend_Db::FETCH_MODE => Zend_Db::FETCH_OBJ + ); + /** * Set the configuration that stores the available resources * @@ -90,8 +114,8 @@ class DbAdapterFactory implements ConfigAwareFactory */ public static function resetConfig() { - unset(self::$resources); - unset(self::$factoryClass); + self::$resources = null; + self::$factoryClass = null; } /** @@ -126,7 +150,11 @@ class DbAdapterFactory implements ConfigAwareFactory /** * Get the resource with the given $identifier * - * @param $identifier The name of the resource + * @throws ConfigurationError + * @throws ProgrammingError + * @param string $identifier The name of the resource + * + * @return Zend_Db_Adapter_Abstract */ public static function getDbAdapter($identifier) { @@ -158,22 +186,24 @@ class DbAdapterFactory implements ConfigAwareFactory * @param mixed $config The configuration section containing the * db information * - * @return \Zend_Db_Adapter_Abstract The created Zend_Db_Adapter + * @return Zend_Db_Adapter_Abstract The created Zend_Db_Adapter * - * @throws \ConfigurationError When the specified db type is invalid + * @throws ConfigurationError When the specified db type is invalid */ private static function createDbAdapter($config) { if ($config->type !== 'db') { - throw new ConfigurationError( - 'Resource type must be "db" but is "' . $config->type . '"' - ); + $msg = 'Resource type must be "db" but is "' . $config->type . '"'; + Logger::error($msg); + throw new ConfigurationError($msg); } $options = array( - 'dbname' => $config->dbname, - 'host' => $config->host, - 'username' => $config->username, - 'password' => $config->password, + 'dbname' => $config->dbname, + 'host' => $config->host, + 'username' => $config->username, + 'password' => $config->password, + 'options' => self::$defaultZendDbAdapterOptions, + 'driver_options' => self::$defaultPdoDriverOptions ); switch ($config->db) { case 'mysql': @@ -181,22 +211,72 @@ class DbAdapterFactory implements ConfigAwareFactory case 'pgsql': return self::callFactory('Pdo_Pgsql', $options); default: - throw new ConfigurationError('Unsupported db type ' . $config->db . '.'); + if (!$config->db) { + $msg = 'Database type is missing (e.g. db=mysql).'; + } else { + $msg = 'Unsupported db type ' . $config->db . '.'; + } + Logger::error($msg); + throw new ConfigurationError($msg); } } /** * Call the currently set factory class * - * @param $adapter The name of the used db adapter - * @param $options OPTIONAL: an array or Zend_Config object with adapter - * parameters + * @param string $adapter The name of the used db adapter + * @param array $options An array or Zend_Config object with adapter + * parameters * * @return Zend_Db_Adapter_Abstract The created adapter */ - private static function callFactory($adapter, $options) + private static function callFactory($adapter, array $options) { $factory = self::$factoryClass; + + $optionModifierCallback = __CLASS__. '::get'. ucfirst(str_replace('_', '', $adapter)). 'Options'; + + if (is_callable($optionModifierCallback)) { + $options = call_user_func($optionModifierCallback, $options); + } + return $factory::factory($adapter, $options); } + + /** + * Get modified attributes for driver PDO_Mysql + * + * @param array $options + * + * @return array + */ + private static function getPdoMysqlOptions(array $options) + { + // To get response for lazy sql statements + $options['driver_options'][PDO::MYSQL_ATTR_INIT_COMMAND] = + 'SET SESSION SQL_MODE=\'STRICT_ALL_TABLES,NO_ZERO_IN_DATE,' + . 'NO_ZERO_DATE,NO_ENGINE_SUBSTITUTION\';'; + + if (!isset($options['port'])) { + $options['port'] = 3306; + } + + return $options; + } + + /** + * Get modified attributes for driver PDO_PGSQL + * + * @param array $options + * + * @return array + */ + private static function getPdoPgsqlOptions(array $options) + { + if (!isset($options['port'])) { + $options['port'] = 5432; + } + + return $options; + } } diff --git a/library/Icinga/Data/Db/Connection.php b/library/Icinga/Data/Db/Connection.php index b118ffb70..2a8d118fe 100644 --- a/library/Icinga/Data/Db/Connection.php +++ b/library/Icinga/Data/Db/Connection.php @@ -1,115 +1,129 @@ + * @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2 + * @author Icinga Development Team + */ +// {{{ICINGA_LICENSE_HEADER}}} namespace Icinga\Data\Db; -use Icinga\Data\DatasourceInterface; -use Icinga\Exception\ConfigurationError; -use Zend_Config as ZfConfig; -use Zend_Db as ZfDb; -use PDO; +use \PDO; +use \Zend_Config; +use \Zend_Db; +use \Zend_Db_Adapter_Abstract; +use \Icinga\Application\DbAdapterFactory; +use \Icinga\Data\DatasourceInterface; +use \Icinga\Exception\ConfigurationError; +use \Icinga\Application\Logger; +/** + * Encapsulate database connections and query creation + */ class Connection implements DatasourceInterface { + /** + * Database connection + * + * @var Zend_Db_Adapter_Abstract + */ protected $db; - protected $config; - protected $dbtype; - public function __construct(ZfConfig $config = null) + /** + * Backend configuration + * + * @var Zend_Config + */ + protected $config; + + /** + * Database type + * + * @var string + */ + protected $dbType; + + /** + * Create a new connection object + * + * @param Zend_Config $config + */ + public function __construct(Zend_Config $config = null) { $this->config = $config; $this->connect(); - $this->init(); } + /** + * Prepare query object + * + * @return Query + */ public function select() { return new Query($this); } + /** + * Getter for database type + * + * @return string + */ public function getDbType() { - return $this->dbtype; + return $this->dbType; } + /** + * Getter for database object + * + * @return Zend_Db_Adapter_Abstract + */ public function getDb() { return $this->db; } - protected function init() + /** + * Create a new connection + */ + private function connect() { - } + $resourceName = $this->config->get('resource'); + $this->db = DbAdapterFactory::getDbAdapter($resourceName); - protected function connect() - { - $this->dbtype = $this->config->get('dbtype', 'mysql'); - - $options = array( - ZfDb::AUTO_QUOTE_IDENTIFIERS => false, - ZfDb::CASE_FOLDING => ZfDb::CASE_LOWER - ); - - $drv_options = array( - PDO::ATTR_TIMEOUT => 2, - // TODO: Check whether LC is useful. Zend_Db does fetchNum for Oci: - PDO::ATTR_CASE => PDO::CASE_LOWER - // TODO: ATTR_ERRMODE => ERRMODE_EXCEPTION vs ERRMODE_SILENT - ); - - switch ($this->dbtype) { - case 'mysql': - $adapter = 'Pdo_Mysql'; - $drv_options[\PDO::MYSQL_ATTR_INIT_COMMAND] = - "SET SESSION SQL_MODE='STRICT_ALL_TABLES,NO_ZERO_IN_DATE," - . "NO_ZERO_DATE,NO_ENGINE_SUBSTITUTION';"; - // Not using ONLY_FULL_GROUP_BY as of performance impact - $port = $this->config->get('port', 3306); - break; - case 'pgsql': - $adapter = 'Pdo_Pgsql'; - $port = $this->config->get('port', 5432); - break; - case 'oracle': - $adapter = 'Pdo_Oci'; - // $adapter = 'Oracle'; - $port = $this->config->get('port', 1521); -// $drv_options[PDO::ATTR_STRINGIFY_FETCHES] = true; - - if ($adapter === 'Oracle') { - // Unused right now - putenv('ORACLE_SID=XE'); - putenv('ORACLE_HOME=/u01/app/oracle/product/11.2.0/xe'); - putenv('PATH=$PATH:$ORACLE_HOME/bin'); - putenv('ORACLE_BASE=/u01/app/oracle'); - putenv('NLS_LANG=AMERICAN_AMERICA.UTF8'); - - } - - break; - default: - throw new ConfigurationError(sprintf( - 'Backend "%s" is not supported', $type - )); - } - $attributes = array( - 'host' => $this->config->host, - 'port' => $port, - 'username' => $this->config->user, - 'password' => $this->config->pass, - 'dbname' => $this->config->db, - 'options' => $options, - 'driver_options' => $drv_options - ); - if ($this->dbtype === 'oracle') { - $attributes['persistent'] = true; - } - $this->db = ZfDb::factory($adapter, $attributes); - if ($adapter === 'Oracle') { - $this->db->setLobAsString(false); + if ($this->db->getConnection() instanceof PDO) { + $this->dbType = $this->db->getConnection()->getAttribute(PDO::ATTR_DRIVER_NAME); + } else { + $this->dbType = strtolower(get_class($this->db->getConnection())); } - // TODO: ZfDb::FETCH_ASSOC for Oracle? - $this->db->setFetchMode(ZfDb::FETCH_OBJ); + if ($this->dbType === null) { + Logger::warn('Could not determine database type'); + } + if ($this->dbType === 'oci') { + $this->dbType = 'oracle'; + } } } diff --git a/modules/monitoring/test/php/testlib/MonitoringControllerTest.php b/modules/monitoring/test/php/testlib/MonitoringControllerTest.php index 6475e8e8c..e35c1655c 100644 --- a/modules/monitoring/test/php/testlib/MonitoringControllerTest.php +++ b/modules/monitoring/test/php/testlib/MonitoringControllerTest.php @@ -1,5 +1,29 @@ + * @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2 + * @author Icinga Development Team + */ // {{{ICINGA_LICENSE_HEADER}}} namespace Icinga\Web\Controller @@ -81,13 +105,15 @@ namespace Test\Monitoring\Testlib { require_once 'Zend/View.php'; - use Icinga\Protocol\Statusdat\Reader; - use Icinga\Web\Controller\ActionController; - use Test\Monitoring\Testlib\DataSource\TestFixture; - use Test\Monitoring\Testlib\DataSource\DataSourceTestSetup; - use Monitoring\Backend\Ido; - use Monitoring\Backend\Statusdat; use \Zend_View; + use \Zend_Config; + use \Icinga\Protocol\Statusdat\Reader; + use \Icinga\Web\Controller\ActionController; + use \Icinga\Application\DbAdapterFactory; + use \Monitoring\Backend\Ido; + use \Monitoring\Backend\Statusdat; + use \Test\Monitoring\Testlib\DataSource\TestFixture; + use \Test\Monitoring\Testlib\DataSource\DataSourceTestSetup; /** * Base class for monitoring controllers that loads required dependencies @@ -174,6 +200,7 @@ namespace Test\Monitoring\Testlib */ private function requireIDOQueries() { + require_once('Application/DbAdapterFactory.php'); require_once('library/Monitoring/Backend/Ido.php'); $this->requireFolder('library/Monitoring/Backend/Ido/Query'); } @@ -223,7 +250,8 @@ namespace Test\Monitoring\Testlib /** * Require and set up a controller $controller using the backend type specified at $backend * - * @param string $controller The name of the controller tu use (must be under monitoring/application/controllers) + * @param string $controller The name of the controller tu use + * (must be under monitoring/application/controllers) * @param string $backend The backend to use ('mysql', 'pgsql' or 'statusdat') * @return ModuleActionController The newly created controller */ @@ -260,23 +288,44 @@ namespace Test\Monitoring\Testlib * @param string $type The type of the backend 'mysql', 'pgsql' or 'statusdat' * @return Ido|Statusdat The newly created backend */ - public function getBackendFor($type) { + public function getBackendFor($type) + { if ($type == "mysql" || $type == "pgsql") { $this->requireIDOQueries(); - return new Ido(new \Zend_Config(array( - "dbtype"=> $type, - 'host' => "localhost", - 'user' => "icinga_unittest", - 'pass' => "icinga_unittest", - 'db' => "icinga_unittest" - ))); - } else if ($type == "statusdat") { + + $resourceConfig = array( + 'icinga-db-unittest' => array( + 'type' => 'db', + 'db' => $type, + 'host' => "localhost", + 'username' => "icinga_unittest", + 'password' => "icinga_unittest", + 'dbname' => "icinga_unittest" + ) + ); + + DbAdapterFactory::resetConfig(); + DbAdapterFactory::setConfig($resourceConfig); + + $backendConfig = array( + 'type' => 'db', + 'resource' => 'icinga-db-unittest' + ); + + return new Ido( + new Zend_Config($backendConfig) + ); + } elseif ($type == "statusdat") { $this->requireStatusDatQueries(); - return new Statusdat(new \Zend_Config(array( - 'status_file' => '/tmp/teststatus.dat', - 'objects_file' => '/tmp/testobjects.cache', - 'no_cache' => true - ))); + return new Statusdat( + new \Zend_Config( + array( + 'status_file' => '/tmp/teststatus.dat', + 'objects_file' => '/tmp/testobjects.cache', + 'no_cache' => true + ) + ) + ); } } } diff --git a/test/php/library/Icinga/Application/DbAdapterFactoryTest.php b/test/php/library/Icinga/Application/DbAdapterFactoryTest.php index 55b855db3..a24cd241e 100644 --- a/test/php/library/Icinga/Application/DbAdapterFactoryTest.php +++ b/test/php/library/Icinga/Application/DbAdapterFactoryTest.php @@ -28,25 +28,28 @@ namespace Tests\Icinga\Application; -require_once('Zend/Db.php'); -require_once('Zend/Db/Adapter/Pdo/Mysql.php'); -require_once('Zend/Config.php'); -require_once('Zend/Log.php'); -require_once('Zend/Config.php'); -require_once('../../library/Icinga/Application/Logger.php'); -require_once('library/Icinga/Application/ZendDbMock.php'); -require_once('../../library/Icinga/Exception/ConfigurationError.php'); -require_once('../../library/Icinga/Exception/ProgrammingError.php'); -require_once('../../library/Icinga/Util/ConfigAwareFactory.php'); -require_once('../../library/Icinga/Application/DbAdapterFactory.php'); +require_once 'Zend/Db.php'; +require_once 'Zend/Db/Adapter/Pdo/Mysql.php'; +require_once 'Zend/Config.php'; +require_once 'Zend/Log.php'; +require_once 'Zend/Config.php'; +require_once realpath(__DIR__. '/../../../library/Icinga/Application/ZendDbMock.php'); +require_once realpath(__DIR__. '/../../../../../library/Icinga/Application/Logger.php'); +require_once realpath(__DIR__. '/../../../../../library/Icinga/Exception/ConfigurationError.php'); +require_once realpath(__DIR__. '/../../../../../library/Icinga/Exception/ProgrammingError.php'); +require_once realpath(__DIR__. '/../../../../../library/Icinga/Util/ConfigAwareFactory.php'); +require_once realpath(__DIR__. '/../../../../../library/Icinga/Application/DbAdapterFactory.php'); +use \PDO; +use \Zend_Db; use \Tests\Icinga\Application\ZendDbMock; use \Icinga\Application\DbAdapterFactory; /* * Unit test for the class DbAdapterFactory */ -class DbAdapterFactoryTest extends \PHPUnit_Framework_TestCase { +class DbAdapterFactoryTest extends \PHPUnit_Framework_TestCase +{ /** * The resources used for this test @@ -60,7 +63,7 @@ class DbAdapterFactoryTest extends \PHPUnit_Framework_TestCase { { $this->resources = array( /* - * PostgreSQL databse + * PostgreSQL database */ 'resource1' => array( 'type' => 'db', @@ -68,7 +71,17 @@ class DbAdapterFactoryTest extends \PHPUnit_Framework_TestCase { 'dbname' => 'resource1', 'host' => 'host1', 'username' => 'username1', - 'password' => 'password1' + 'password' => 'password1', + 'options' => array( + Zend_Db::AUTO_QUOTE_IDENTIFIERS => false, + Zend_Db::CASE_FOLDING => Zend_Db::CASE_LOWER, + Zend_Db::FETCH_MODE => Zend_Db::FETCH_OBJ + ), + 'driver_options' => array( + PDO::ATTR_TIMEOUT => 2, + PDO::ATTR_CASE => PDO::CASE_LOWER + ), + 'port' => 5432 ), /* * MySQL database @@ -79,7 +92,19 @@ class DbAdapterFactoryTest extends \PHPUnit_Framework_TestCase { 'dbname' => 'resource2', 'host' => 'host2', 'username' => 'username2', - 'password' => 'password2' + 'password' => 'password2', + 'options' => array( + Zend_Db::AUTO_QUOTE_IDENTIFIERS => false, + Zend_Db::CASE_FOLDING => Zend_Db::CASE_LOWER, + Zend_Db::FETCH_MODE => Zend_Db::FETCH_OBJ + ), + 'driver_options' => array( + PDO::ATTR_TIMEOUT => 2, + PDO::ATTR_CASE => PDO::CASE_LOWER, + PDO::MYSQL_ATTR_INIT_COMMAND => 'SET SESSION SQL_MODE=\'STRICT_ALL_TABLES,NO_ZERO_IN_DATE,' + . 'NO_ZERO_DATE,NO_ENGINE_SUBSTITUTION\';' + ), + 'port' => 3306 ), /* * Unsupported database type @@ -102,7 +127,7 @@ class DbAdapterFactoryTest extends \PHPUnit_Framework_TestCase { DbAdapterFactory::setConfig( $this->resources, array( - 'factory' => 'Tests\Icinga\Application\ZendDbMock' + 'factory' => '\Tests\Icinga\Application\ZendDbMock' ) ); } @@ -113,7 +138,8 @@ class DbAdapterFactoryTest extends \PHPUnit_Framework_TestCase { $this->assertEquals( 'Pdo_Mysql', ZendDbMock::getAdapter(), - 'The db adapter name must be Pdo_Mysql.'); + 'The db adapter name must be Pdo_Mysql.' + ); $this->assertEquals( $this->getOptions($this->resources['resource2']), ZendDbMock::getConfig(), @@ -123,24 +149,31 @@ class DbAdapterFactoryTest extends \PHPUnit_Framework_TestCase { public function testResourceExists() { - $this->assertTrue(DbAdapterFactory::resourceExists('resource2'), - 'resourceExists() called with an existing resource should return true'); + $this->assertTrue( + DbAdapterFactory::resourceExists('resource2'), + 'resourceExists() called with an existing resource should return true' + ); - $this->assertFalse(DbAdapterFactory::resourceExists('not existing'), - 'resourceExists() called with an existing resource should return false'); + $this->assertFalse( + DbAdapterFactory::resourceExists('not existing'), + 'resourceExists() called with an existing resource should return false' + ); - $this->assertFalse(DbAdapterFactory::resourceExists('resource4'), - 'resourceExists() called with an incompatible resource should return false'); + $this->assertFalse( + DbAdapterFactory::resourceExists('resource4'), + 'resourceExists() called with an incompatible resource should return false' + ); } public function testGetResources() { - $withoutIncompatible = array_merge(array(),$this->resources); + $withoutIncompatible = array_merge(array(), $this->resources); unset($withoutIncompatible['resource4']); $this->assertEquals( $withoutIncompatible, DbAdapterFactory::getResources(), - 'getResources should return an array of all existing resources that are compatible'); + 'getResources should return an array of all existing resources that are compatible' + ); } /** @@ -172,7 +205,7 @@ class DbAdapterFactoryTest extends \PHPUnit_Framework_TestCase { */ private function getOptions($config) { - $options = array_merge(array(),$config); + $options = array_merge(array(), $config); unset($options['type']); unset($options['db']); return $options;