diff --git a/app/plugins/CoreServer/src/CoreServerPlugin.php b/app/plugins/CoreServer/src/CoreServerPlugin.php index 310c7ccef..9ba9a536c 100644 --- a/app/plugins/CoreServer/src/CoreServerPlugin.php +++ b/app/plugins/CoreServer/src/CoreServerPlugin.php @@ -33,7 +33,7 @@ public function bootstrap(PluginApplicationInterface $app): void $oracleEnabled = \Cake\Core\Configure::read('registry.database.oracle.enable'); if($oracleEnabled === true) { - $app->addPlugin(\CakeDC\OracleDriver\Plugin::class, ['bootstrap' => true]); + $app->addPlugin(\Ioigoume\OracleDriver\Plugin::class, ['bootstrap' => true]); } } catch(\Error $e) { diff --git a/app/plugins/CoreServer/src/Model/Table/HttpServersTable.php b/app/plugins/CoreServer/src/Model/Table/HttpServersTable.php index 6eeff5cd7..5bd3c3aad 100644 --- a/app/plugins/CoreServer/src/Model/Table/HttpServersTable.php +++ b/app/plugins/CoreServer/src/Model/Table/HttpServersTable.php @@ -176,4 +176,48 @@ public function validationDefault(Validator $validator): Validator { return $validator; } + + + /** + * Test TCP connectivity to the HTTP server. + * + * @param int $serverId ID of the HTTP server configuration + * @param string $name Name of the connection test (for logging) + * @return bool True if connection succeeds, false otherwise + * @since COmanage Registry v5.2.0 + */ + public function connect(int $serverId, string $name): bool + { + try { + // Fetch the configured HTTP server + $httpServer = $this->get($serverId); + + // Parse URL to extract host and port + $parts = parse_url((string)$httpServer->url); + if ($parts === false || empty($parts['host'])) { + return false; + } + + $host = $parts['host']; + $scheme = $parts['scheme'] ?? 'http'; + $port = isset($parts['port']) + ? (int)$parts['port'] + : ($scheme === 'https' ? 443 : 80); + + // Attempt a TCP connection as a liveness check + $timeout = 5.0; // seconds + $errno = 0; + $errstr = ''; + $conn = @fsockopen($host, $port, $errno, $errstr, $timeout); + + if (is_resource($conn)) { + fclose($conn); + return true; + } + + return false; + } catch (\Throwable $e) { + return false; + } + } } diff --git a/app/plugins/CoreServer/src/Model/Table/SqlServersTable.php b/app/plugins/CoreServer/src/Model/Table/SqlServersTable.php index a5ad9c46f..9acff1d21 100644 --- a/app/plugins/CoreServer/src/Model/Table/SqlServersTable.php +++ b/app/plugins/CoreServer/src/Model/Table/SqlServersTable.php @@ -36,6 +36,7 @@ use Cake\ORM\Table; use Cake\Validation\Validator; use CoreServer\Lib\Enum\RdbmsTypeEnum; +use \Cake\ORM\TableRegistry; class SqlServersTable extends Table { use \App\Lib\Traits\AutoViewVarsTrait; @@ -44,6 +45,7 @@ class SqlServersTable extends Table { use \App\Lib\Traits\PrimaryLinkTrait; use \App\Lib\Traits\TableMetaTrait; use \App\Lib\Traits\ValidationTrait; + use \App\Lib\Traits\LabeledLogTrait; /** * Perform Cake Model initialization. @@ -162,9 +164,11 @@ public function connect(int $serverId, string $name): bool { // We don't test that the plugin is available here, an error should be thrown // when we try to connect. - $dbconfig['className'] = 'CakeDC\OracleDriver\Database\OracleConnection'; - $dbconfig['driver'] = 'CakeDC\OracleDriver\Database\Driver\OracleOCI'; // For OCI8 + $dbconfig['className'] = 'Ioigoume\OracleDriver\Database\OracleConnection'; + $dbconfig['driver'] = 'Ioigoume\OracleDriver\Database\Driver\OracleOCI'; // For OCI8 $dbconfig['quoteIdentifiers'] = true; + $dbconfig['init'] = []; + $dbconfig['flags'] = []; // Use 'CakeDC\\OracleDriver\\Database\\Driver\\OraclePDO' for PDO_OCI, but CakeDC // recommends OCI8 @@ -190,6 +194,87 @@ public function connect(int $serverId, string $name): bool { return true; } + + /** + * Test database connectivity using ORM query to a known table. + * + * @param string $connectionName Connection name to test + * @return bool True if connection and query successful, false otherwise + * @throws \Cake\Database\Exception\DatabaseException When database connection fails + * @since COmanage Registry v5.0.0 + */ + public function pingWithOrmFindAuto(string $connectionName = 'my_temp_connection'): bool + { + $conn = ConnectionManager::get($connectionName); + + // Pick any table visible to the connection + $tables = $conn->getSchemaCollection()->listTables(); + if (empty($tables)) { + // No tables to query, cannot ORM-ping + return false; + } + + $this->llog('debug', 'pingWithOrmFindAuto tables: ' . var_export($tables, true)); + + $tableName = 'cos'; + + $alias = 'TmpPing_' . $connectionName . '_' . $tableName; + $locator = TableRegistry::getTableLocator(); + $table = $locator->get($alias, [ + 'table' => $tableName, + 'connection' => $conn, + ]); + + $row = $table->find() + ->select(['cnt' => $table->find()->func()->count('*')]) + ->enableHydration(false) + ->first(); + + $this->llog('debug', 'pingWithOrmFindAuto row result: ' . var_export($row, true)); + + + return $row !== null; + } + + + /** + * Try to connect to the configured connection and run a minimal probe. + * + * @param string $connectionName ConnectionManager name (eg: 'server42') + * @return bool True if the probe succeeds, false otherwise + */ + public function pingDb(string $connectionName = 'my_temp_connection'): bool + { + try { + $conn = ConnectionManager::get($connectionName); + // Force immediate connection attempt +// $conn->connect(); + + // Use an engine-appropriate lightweight query + $sql = ($conn->getDriver() instanceof \Ioigoume\OracleDriver\Database\Driver\OracleOCI) + ? 'SELECT 1 FROM DUAL' + : 'SELECT 1'; + + $val = $conn->execute($sql)->fetchColumn(0); + $ok = ((int)$val === 1); + + if (!$ok) { + $this->llog( + 'debug', + sprintf("DB ping for connection '%s' returned unexpected value: %s", $connectionName, var_export($val, true)) + ); + } + + return $ok; + } catch (\Throwable $e) { + $this->llog( + 'error', + sprintf("DB ping failed for connection '%s': %s", $connectionName, $e->getMessage()) + ); + return false; + } + } + /** * Application Rule to determine if Oracle is enabled (if selected). * diff --git a/app/resources/locales/en_US/operation.po b/app/resources/locales/en_US/operation.po index bfd24d042..0b7da9324 100644 --- a/app/resources/locales/en_US/operation.po +++ b/app/resources/locales/en_US/operation.po @@ -153,6 +153,9 @@ msgstr "Configure {0}" msgid "configure.plugin" msgstr "Configure Plugin" +msgid "connection.test" +msgstr "Test Connection" + msgid "continue" msgstr "Continue" @@ -327,6 +330,15 @@ msgstr "Resend" msgid "resume" msgstr "Resume" +msgid "rs.test.ok" +msgstr "Connection OK" + +msgid "rs.test.error" +msgstr "Connection Error" + +msgid "rs.test.na" +msgstr "Connection N/A" + msgid "save" msgstr "Save" diff --git a/app/src/Controller/ServersController.php b/app/src/Controller/ServersController.php index 335d0df83..ee99d5ffa 100644 --- a/app/src/Controller/ServersController.php +++ b/app/src/Controller/ServersController.php @@ -30,6 +30,7 @@ namespace App\Controller; // XXX not doing anything with Log yet +use App\Lib\Util\StringUtilities; use Cake\Log\Log; class ServersController extends StandardPluggableController { @@ -38,4 +39,73 @@ class ServersController extends StandardPluggableController { 'Servers.description' => 'asc' ] ]; + + + /** + * Test the connection to a configured server by attempting to establish a connection + * using the server's plugin-specific connection method. + * + * @param string $id Server identifier + * @return \Cake\Http\Response|null Redirects to index view with connection test result + * @throws \Cake\Datasource\Exception\RecordNotFoundException If server not found + */ + public function testconnection(string $id) { + // We basically implement a redirect here to facilitate view rendering. + // (We only need to map into the plugin on actual link click, instead of + // potentially many times on an index view for links that may not be used.) + + /** var Cake\ORM\Table $table */ + $table = $this->getCurrentTable(); + + $serverId = $this->request->getParam('pass')[0]; + $serverObj = $table->findById($serverId) + ->firstOrFail(); + + $pluginTable = $this->getTableLocator()->get($serverObj->plugin); + $pluginObj = $pluginTable->find() + ->where([StringUtilities::tableToForeignKey($table) => $serverId]) + ->firstOrFail(); + + try { + if (method_exists($pluginTable, 'connect')) { + // Set the Connection Manager config + $connection = $pluginTable->connect((int)$serverId, 'server' . $serverId); + + if (method_exists($pluginTable, 'pingWithOrmFindAuto')) { + // This is database. We need to ping the database to be sure + $ok = $pluginTable->pingWithOrmFindAuto('server' . (int)$serverId); + if ($ok) { + $this->Flash->success(__d('operation', 'rs.test.ok')); + Log::debug("Successfully connected to database server " . $serverId); + } else { + $this->Flash->error(__d('operation', 'rs.test.error')); + Log::error("Failed to connect to database server " . $serverId . ": Database ping failed"); + } + } else { + if ($connection) { + $this->Flash->success(__d('operation', 'rs.test.ok')); + Log::debug("Successfully connected to server " . $serverId); + } else { + $this->Flash->error(__d('operation', 'rs.test.error')); + Log::error("Failed to connect to server " . $serverId . ": Connection failed"); + } + } + + } else { + $this->Flash->error(__d('operation', 'rs.test.na')); + } + } catch (\Exception $e) { + $this->Flash->error($e->getMessage()); + } + + $redirect = [ + 'controller' => 'Servers', + 'action' => 'index', + '?' => [ + 'co_id' => $serverObj->co_id + ] + ]; + + return $this->redirect($redirect); + } } \ No newline at end of file diff --git a/app/src/Model/Table/ServersTable.php b/app/src/Model/Table/ServersTable.php index 678b20bb1..bbcc26a19 100644 --- a/app/src/Model/Table/ServersTable.php +++ b/app/src/Model/Table/ServersTable.php @@ -79,6 +79,7 @@ public function initialize(array $config): void { $this->setDisplayField('description'); $this->setPrimaryLink('co_id'); + $this->setAllowLookupPrimaryLink(['testconnection']); $this->setRequiresCO(true); $this->setAutoViewVars([ @@ -96,6 +97,7 @@ public function initialize(array $config): void { // Actions that operate over an entity (ie: require an $id) 'entity' => [ 'configure' => ['platformAdmin', 'coAdmin'], + 'testconnection' => ['platformAdmin', 'coAdmin'], 'delete' => ['platformAdmin', 'coAdmin'], 'edit' => ['platformAdmin', 'coAdmin'], 'view' => ['platformAdmin', 'coAdmin'] diff --git a/app/templates/Servers/columns.inc b/app/templates/Servers/columns.inc index 19e4ae6fa..36462d44d 100644 --- a/app/templates/Servers/columns.inc +++ b/app/templates/Servers/columns.inc @@ -47,6 +47,11 @@ $rowActions = [ 'action' => 'configure', 'label' => __d('operation', 'configure.plugin'), 'icon' => 'electrical_services' + ], + [ + 'action' => 'testconnection', + 'label' => __d('operation', 'connection.test'), + 'icon' => 'fork_right' ] ];