Skip to content

Test Server Configuration #344

Draft
wants to merge 3 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/plugins/CoreServer/src/CoreServerPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
44 changes: 44 additions & 0 deletions app/plugins/CoreServer/src/Model/Table/HttpServersTable.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
}
89 changes: 87 additions & 2 deletions app/plugins/CoreServer/src/Model/Table/SqlServersTable.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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.
Expand Down Expand Up @@ -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
Expand All @@ -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).
*
Expand Down
12 changes: 12 additions & 0 deletions app/resources/locales/en_US/operation.po
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,9 @@ msgstr "Configure {0}"
msgid "configure.plugin"
msgstr "Configure Plugin"

msgid "connection.test"
msgstr "Test Connection"

msgid "continue"
msgstr "Continue"

Expand Down Expand Up @@ -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"

Expand Down
70 changes: 70 additions & 0 deletions app/src/Controller/ServersController.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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);
}
}
2 changes: 2 additions & 0 deletions app/src/Model/Table/ServersTable.php
Original file line number Diff line number Diff line change
Expand Up @@ -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([
Expand All @@ -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']
Expand Down
5 changes: 5 additions & 0 deletions app/templates/Servers/columns.inc
Original file line number Diff line number Diff line change
Expand Up @@ -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'
]
];

Expand Down