Compare commits

...

5 Commits

Author SHA1 Message Date
odain
2dd30fc55a cleanup 2026-07-01 18:02:01 +02:00
odain
7a70e2460a N°9675 - geptile PR fixes + enhance regexp for cli php version 2026-07-01 17:51:13 +02:00
odain
e27dcbfb2d N°9675 - raise PHP CLI version issue as well 2026-07-01 17:04:56 +02:00
odain
825c9f3a0e N°9675 - enhance setup data consistency error feedback 2026-07-01 15:53:41 +02:00
odain
3024b1d492 N°9675 - raise first exception encountered when starting metamodel 2026-07-01 15:10:46 +02:00
4 changed files with 165 additions and 48 deletions

View File

@@ -5761,36 +5761,37 @@ abstract class MetaModel
self::$m_sEnvironment = $sEnvironment;
try {
if (!defined('MODULESROOT')) {
define('MODULESROOT', APPROOT.'env-'.self::$m_sEnvironment.'/');
if (!defined('MODULESROOT')) {
define('MODULESROOT', APPROOT.'env-'.self::$m_sEnvironment.'/');
self::$m_bTraceSourceFiles = $bTraceSourceFiles;
self::$m_bTraceSourceFiles = $bTraceSourceFiles;
// $config can be either a filename, or a Configuration object (volatile!)
if ($config instanceof Config) {
self::LoadConfig($config, $bAllowCache);
} else {
self::LoadConfig(new Config($config), $bAllowCache);
}
if ($bModelOnly) {
return;
}
// $config can be either a filename, or a Configuration object (volatile!)
if ($config instanceof Config) {
self::LoadConfig($config, $bAllowCache);
} else {
self::LoadConfig(new Config($config), $bAllowCache);
}
CMDBSource::SelectDB(self::$m_sDBName);
foreach (MetaModel::EnumPlugins('ModuleHandlerApiInterface') as $oPHPClass) {
$oPHPClass::OnMetaModelStarted();
if ($bModelOnly) {
// Event service must be initialized after the MetaModel startup, otherwise it cannot discover classes implementing the iEventServiceSetup interface
EventService::InitService();
EventService::FireEvent(new EventData(ApplicationEvents::APPLICATION_EVENT_METAMODEL_STARTED));
return;
}
ExpressionCache::Warmup();
} finally {
// Event service must be initialized after the MetaModel startup, otherwise it cannot discover classes implementing the iEventServiceSetup interface
EventService::InitService();
EventService::FireEvent(new EventData(ApplicationEvents::APPLICATION_EVENT_METAMODEL_STARTED));
}
CMDBSource::SelectDB(self::$m_sDBName);
foreach (MetaModel::EnumPlugins('ModuleHandlerApiInterface') as $oPHPClass) {
$oPHPClass::OnMetaModelStarted();
}
ExpressionCache::Warmup();
// Event service must be initialized after the MetaModel startup, otherwise it cannot discover classes implementing the iEventServiceSetup interface
EventService::InitService();
EventService::FireEvent(new EventData(ApplicationEvents::APPLICATION_EVENT_METAMODEL_STARTED));
}
/**

View File

@@ -2,9 +2,7 @@
namespace Combodo\iTop\Setup\FeatureRemoval;
use ContextTag;
use CoreException;
use Exception;
use IssueLog;
use SetupLog;
use utils;
@@ -31,42 +29,82 @@ class ModelReflectionSerializer
self::$oInstance = $oInstance;
}
public const ERROR_LABEL = "Data consistency check failed: %s";
public function GetModelFromEnvironment(string $sEnv): array
{
IssueLog::Debug(__METHOD__, null, ['env' => $sEnv]);
$sPHPExec = trim(utils::GetConfig()->Get('php_path'));
$sOutput = "";
$aOutput = null;
$iRes = 0;
$sCommandLine = sprintf("$sPHPExec %s/get_model_reflection.php --env=%s", __DIR__, escapeshellarg($sEnv));
exec($sCommandLine, $sOutput, $iRes);
exec("$sPHPExec --version", $aOutput, $iRes);
if ($iRes != 0) {
$this->LogErrorWithProperLogger("Cannot get classes", null, ['env' => $sEnv, 'code' => $iRes, "output" => $sOutput, 'cmd' => $sCommandLine]);
throw new CoreException("Cannot get classes from env ".$sEnv);
$sError = sprintf(self::ERROR_LABEL, "Cannot check CLI/PHP version ($sPHPExec)");
$this->LogSetupError($sError, null, ['env' => $sEnv, 'code' => $iRes, "output" => $aOutput, 'php_path' => $sPHPExec]);
throw new CoreException($sError);
}
$aClasses = json_decode($sOutput[0] ?? null, true);
$this->CheckCliPhpVersionFromOutput(PHP_MAJOR_VERSION.'.'.PHP_MINOR_VERSION, $sPHPExec, $aOutput);
$aOutput = null;
$iRes = 0;
//preliminary check
$sEnvDir = APPROOT."env-$sEnv";
if (! is_dir($sEnvDir)) {
$sMsg = sprintf(self::ERROR_LABEL, "Missing environment ($sEnvDir)");
$this->LogSetupError($sMsg);
throw new CoreException($sMsg);
}
$sConfigFile = APPROOT."conf/$sEnv/config-itop.php";
if (! is_file($sConfigFile)) {
$sMsg = sprintf(self::ERROR_LABEL, "Missing configuration ($sConfigFile)");
$this->LogSetupError($sMsg);
throw new CoreException($sMsg);
}
$sCommandLine = sprintf("$sPHPExec %s/get_model_reflection.php --env=%s", __DIR__, escapeshellarg($sEnv));
exec($sCommandLine, $aOutput, $iRes);
if ($iRes != 0) {
$sError = $aOutput[0] ?? 'Invalid output when serializing model';
$this->LogSetupError(sprintf(self::ERROR_LABEL, '(cli error) '.$sError), null, ['env' => $sEnv, 'code' => $iRes, "output" => $aOutput, 'cmd' => $sCommandLine]);
throw new CoreException(sprintf(self::ERROR_LABEL, $sError));
}
$aClasses = json_decode($aOutput[0] ?? null, true);
if (false === $aClasses) {
$this->LogErrorWithProperLogger("Invalid JSON", null, ['env' => $sEnv, "output" => $sOutput]);
throw new Exception("cannot get classes");
$sMsg = sprintf(self::ERROR_LABEL, 'Invalid JSON');
$this->LogSetupError($sMsg, null, ['env' => $sEnv, "output" => $aOutput]);
throw new CoreException($sMsg);
}
if (!is_array($aClasses)) {
$this->LogErrorWithProperLogger("not an array", null, ['env' => $sEnv, "classes" => $aClasses, "output" => $sOutput]);
throw new Exception("cannot get classes from $sEnv");
$sError = $aOutput[0] ?? 'Invalid json array when serializing model';
$this->LogSetupError(sprintf(self::ERROR_LABEL, '(JSON output not an array) '.$sError), null, ['env' => $sEnv, "classes" => $aClasses, "output" => $aOutput]);
throw new CoreException(sprintf(self::ERROR_LABEL, $sError));
}
return $aClasses;
}
//could be shared with others in log APIs ?
private function LogErrorWithProperLogger($sMessage, $sChannel = null, $aContext = []): void
public function CheckCliPhpVersionFromOutput(string $sUIPhpVersion, string $sPHPExec, $aOutput): void
{
if (ContextTag::Check(ContextTag::TAG_SETUP)) {
SetupLog::Error($sMessage, $sChannel, $aContext);
} else {
IssueLog::Error($sMessage, $sChannel, $aContext);
$sFoundVersion = trim($aOutput[0] ?? "");
if (preg_match('/(\d+\.\d+)(?:\.\d+)?/', $sFoundVersion, $aMatches)) {
$sFoundVersion = $aMatches[1];
}
if ($sFoundVersion != $sUIPhpVersion) {
$sError = sprintf(self::ERROR_LABEL, "Invalid PHP versions (CLI: $sFoundVersion/ UI: $sUIPhpVersion)");
$this->LogSetupError($sError, null, ["output" => $aOutput, 'php_path' => $sPHPExec]);
throw new CoreException($sError);
}
}
//could be shared with others in log APIs ?
private function LogSetupError($sMessage, $sChannel = null, $aContext = []): void
{
SetupLog::Enable(APPROOT.'log/setup.log');
SetupLog::Error($sMessage, $sChannel, $aContext);
}
}

View File

@@ -21,8 +21,7 @@ $sConfFile = utils::GetConfigFilePath($sEnv);
try {
MetaModel::Startup($sConfFile, false /* $bModelOnly */, false /* $bAllowCache */, false /* $bTraceSourceFiles */, $sEnv);
} catch (\Throwable $e) {
echo $e->getMessage();
echo $e->getTraceAsString();
SetupLog::Enable(APPROOT.'log/setup.log');
\SetupLog::Error(
"Cannot read model from provided environment",
null,
@@ -32,7 +31,9 @@ try {
'stack' => $e->getTraceAsString(),
]
);
echo "Cannot read model from provided environment";
//keep first echo to have proper setup feedbacks
echo $e->getMessage();
exit(1);
}

View File

@@ -20,10 +20,87 @@ class ModelSerializationTest extends ItopDataTestCase
$this->assertEqualsCanonicalizing(MetaModel::GetClasses(), $aModel);
}
public function testGetModelFromEnvironmentFailure()
public function testCheckFail()
{
$sOuput = <<<OUTPUT
PHP 7.4.33 (cli) (built: Aug 2 2024 16:22:28) ( NTS )
OUTPUT;
$this->expectException(\CoreException::class);
$this->expectExceptionMessage("Data consistency check failed: Invalid PHP versions (CLI: 7.4/ UI: 6.6)");
ModelReflectionSerializer::GetInstance()->CheckCliPhpVersionFromOutput('6.6', 'sPHPExec', [$sOuput]);
}
public function CheckOKProvider()
{
return [
["7.4 7.2 7.3.33"],
["PHP 7.4.33 (cli) (built: Aug 2 2024 16:22:28) ( NTS )"],
["PHP 7.4.33 PHP 7.33.22"],
["version: 7.4.27 stable"],
];
}
/**
* @dataProvider CheckOKProvider
*/
public function testCheckOK($sOuput)
{
ModelReflectionSerializer::GetInstance()->CheckCliPhpVersionFromOutput('7.4', 'sPHPExec', [$sOuput]);
$this->assertTrue(true);
}
public function testGetModelFromEnvironmentFailure_NoEnvt()
{
$this->expectException(\CoreException::class);
$this->expectExceptionMessage("Cannot get classes");
$sEnvDir = APPROOT."env-gabuzomeu";
$this->expectExceptionMessage("Data consistency check failed: Missing environment ($sEnvDir)");
ModelReflectionSerializer::GetInstance()->GetModelFromEnvironment('gabuzomeu');
}
public function testGetModelFromEnvironmentFailure_NoConfiguration()
{
$sEnvDir = APPROOT."env-gabuzomeu";
$this->aFileToClean [] = $sEnvDir;
mkdir($sEnvDir);
$this->expectException(\CoreException::class);
$sConfigFile = APPROOT."conf/gabuzomeu/config-itop.php";
$this->expectExceptionMessage("Data consistency check failed: Missing configuration ($sConfigFile)");
ModelReflectionSerializer::GetInstance()->GetModelFromEnvironment('gabuzomeu');
}
public function testGetModelFromEnvironmentFailure_BrokenConfiguration()
{
$sEnvDir = APPROOT."env-gabuzomeu";
mkdir($sEnvDir);
$this->aFileToClean [] = $sEnvDir;
mkdir(APPROOT."conf/gabuzomeu");
$this->aFileToClean [] = APPROOT."conf/gabuzomeu";
$sConfigFile = APPROOT."conf/gabuzomeu/config-itop.php";
touch($sConfigFile);
file_put_contents($sConfigFile, 'invalid php content...');
$this->expectException(\CoreException::class);
$sError = <<<ERROR
Syntax error in configuration file: file = $sConfigFile, error = <tt>invalid php content...</tt>
ERROR;
$this->expectExceptionMessage("Data consistency check failed: $sError");
ModelReflectionSerializer::GetInstance()->GetModelFromEnvironment('gabuzomeu');
}
public function testGetModelFromEnvironmentFailure_ItopInMaintenanceMode()
{
touch(MAINTENANCE_MODE_FILE);
$this->aFileToClean [] = MAINTENANCE_MODE_FILE;
$this->expectException(\CoreException::class);
$sError = <<<ERROR
This application is currently under maintenance.
ERROR;
$this->expectExceptionMessage("Data consistency check failed: $sError");
ModelReflectionSerializer::GetInstance()->GetModelFromEnvironment($this->GetTestEnvironment());
}
}