Merge branch 'support/3.0' into saas/3.0

This commit is contained in:
odain
2023-05-22 14:08:02 +02:00
609 changed files with 10493 additions and 7385 deletions

View File

@@ -0,0 +1,343 @@
<?php
/**
* Copyright (C) 2013-2021 Combodo SARL
*
* This file is part of iTop.
*
* iTop is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* iTop is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
*/
require_once ('../../../approot.inc.php');
require_once(APPROOT.'application/application.inc.php');
require_once(APPROOT.'application/itopwebpage.class.inc.php');
require_once(APPROOT.'application/startup.inc.php');
require_once(APPROOT.'application/loginwebpage.class.inc.php');
/////////////////////////////////////////////////////////////////////
// Main program
//
LoginWebPage::DoLogin(true); // Check user rights and prompt if needed
$sSubmit = utils::ReadParam('submit', '', false, 'raw_data');
if ($sSubmit != 'Reset')
{
$sOQL = utils::ReadParam('OQL_Request', '', false, 'raw_data');
}
else
{
$sOQL = '';
}
$bError = false;
$oP = new iTopWebPage('Database inconsistencies');
$oP->set_base(utils::GetAbsoluteUrlAppRoot().'tests/');
$oP->set_title('Grouping with functions');
$oP->add('<div style="padding: 15px;"><h2>Grouping with functions</h2>');
$oP->add('<div style="padding: 15px; background: #ddd;">');
try
{
if (!empty($sOQL))
{
// Getting class attributes
$oSearch = DBSearch::FromOQL($sOQL);
$aSearches = $oSearch->GetSearches();
if ($oSearch instanceof DBUnionSearch)
{
$sClass = $aSearches[0]->GetClassAlias();
$sRealClass = $aSearches[0]->GetClass();
}
else
{
$sClass = $oSearch->GetClassAlias();
$sRealClass = $oSearch->GetClass();
}
$sGroupBy1 = utils::ReadParam('groupby_1', '');
$sGroupBy2 = utils::ReadParam('groupby_2', '');
$sOrderBy1 = utils::ReadParam('orderby_1', '');
$sOrderBy2 = utils::ReadParam('orderby_2', '');
$sAttributesOptions1 = '';
$sAttributesOptions2 = '';
$sAttributesOptions3 = '';
$sAttributesOptions4 = '';
foreach(array('_itop_sum_', '_itop_avg_', '_itop_min_', '_itop_max_', '_itop_count_', 'group1', 'group2') as $sAttCode)
{
$sAttributesOptions3 .= '<option value="'.$sAttCode.'" '.($sOrderBy1 == $sAttCode ? 'selected' : '').'>'.$sAttCode.'</option>';
$sAttributesOptions4 .= '<option value="'.$sAttCode.'" '.($sOrderBy2 == $sAttCode ? 'selected' : '').'>'.$sAttCode.'</option>';
}
foreach(MetaModel::ListAttributeDefs($sRealClass) as $sAttCode => $oAttDef)
{
// Skip this attribute if not defined in this table
if ($oSearch instanceof DBUnionSearch)
{
foreach($aSearches as $oSubQuery)
{
$sSubClass = $oSubQuery->GetClass();
if (!MetaModel::IsValidAttCode($sSubClass, $sAttCode))
{
continue 2;
}
}
}
$sAttributesOptions1 .= '<option value="'.$sAttCode.'" '.($sGroupBy1 == $sAttCode ? 'selected' : '').'>'.$sAttCode.'</option>';
$sAttributesOptions2 .= '<option value="'.$sAttCode.'" '.($sGroupBy2 == $sAttCode ? 'selected' : '').'>'.$sAttCode.'</option>';
}
$iLimit = intval(utils::ReadParam('top', '0'));
$sInvOrder1 = utils::ReadParam('desc1', '');
$sCheck1 = ($sInvOrder1 == 'on' ? 'checked' : '');
$sInvOrder2 = utils::ReadParam('desc2', '');
$sCheck2 = ($sInvOrder2 == 'on' ? 'checked' : '');
$sFuncField = utils::ReadParam('funcfield', '');
$sFuncFieldOption = '';
foreach(MetaModel::ListAttributeDefs($sRealClass) as $sAttCode => $oAttDef)
{
// Skip this attribute if not defined in this table
if ($oSearch instanceof DBUnionSearch)
{
foreach($aSearches as $oSubQuery)
{
$sSubClass = $oSubQuery->GetClass();
if (!MetaModel::IsValidAttCode($sSubClass, $sAttCode))
{
continue 2;
}
}
}
switch (get_class($oAttDef))
{
case 'Integer':
case 'AttributeDecimal':
case 'AttributeDuration':
case 'AttributeSubItem':
case 'AttributePercentage':
$sFuncFieldOption .= '<option value="'.$sAttCode.'" '.($sFuncField == $sAttCode ? 'selected' : '').'>'.$sAttCode.'</option>';
break;
}
}
}
}
catch (Exception $e)
{
$oP->p('<div class="header_message message_error">'.$e->getMessage().'</div>');
$bError = true;
}
$oP->add("<div><form>");
$oP->add("<input type=\"submit\" name=\"submit\" value=\"Reset\">\n");
$oP->add("</form></div>");
$oP->add("<form>");
$oP->add(
<<<EOF
<div>
<label>Search OQL:</label>
<div>
<textarea id='OQL_Request' name='OQL_Request' cols='60' rows='5'>$sOQL</textarea>
</div>
</div>
EOF
);
if (!empty($sOQL) && !$bError)
{
$oP->add(
<<<EOF
<div>
<label>Group by:</label>
<div>
<select id="groupby_1" name="groupby_1">
$sAttributesOptions1
</select>
<select id="groupby_2" name="groupby_2">
<option></option>
$sAttributesOptions2
</select>
</div>
</div>
<div>
<label>Order by:</label>
<div>
<select id="orderby_1" name="orderby_1">$sAttributesOptions3</select>
<label>Inv order</label><input type="checkbox" name="desc1" $sCheck1/>
</div>
<div>
<select id="orderby_2" name="orderby_2">
<option></option>
$sAttributesOptions4
</select>
<label>Inv order</label><input type="checkbox" name="desc2" $sCheck2/>
</div>
</div>
<div>
<label>Functions on:</label>
<div>
<select id="funcfield" name="funcfield">$sFuncFieldOption</select>
</div>
</div>
<div>
<label>Top:</label>
<div><input type="text" id="top" name="top" value="$iLimit"/>
</div>
</div>
EOF
);
}
$oP->add("<input type=\"submit\" name=\"submit\" value=\"Search\">\n");
$oP->add("</form>");
$sSQL = '';
if (empty($sOQL) || empty($sGroupBy1))
{
$oP->output();
return;
}
try
{
$iLimitStart = 0;
$aOrderBy = array();
if (!empty($sOrderBy1))
{
$aOrderBy[$sOrderBy1] = ($sInvOrder1 != 'on');
}
if (!empty($sOrderBy2))
{
$aOrderBy[$sOrderBy2] = ($sInvOrder2 != 'on');
}
$aGroupBy = array();
$oExpr1 = Expression::FromOQL($sClass.'.'.$sGroupBy1);
$aGroupBy["group1"] = $oExpr1;
if (!empty($sGroupBy2))
{
$oExpr2 = Expression::FromOQL($sClass.'.'.$sGroupBy2);
$aGroupBy["group2"] = $oExpr2;
}
$aArgs = array();
if (empty($sFuncField))
{
$aFunctions = array();
}
else
{
$oTimeExpr = Expression::FromOQL($sClass.'.'.$sFuncField);
$oSumExpr = new FunctionExpression('SUM', array($oTimeExpr));
$oAvgExpr = new FunctionExpression('AVG', array($oTimeExpr));
$oMinExpr = new FunctionExpression('MIN', array($oTimeExpr));
$oMaxExpr = new FunctionExpression('MAX', array($oTimeExpr));
// Alias => Expression
$aFunctions = array(
'_itop_sum_' => $oSumExpr,
'_itop_avg_' => $oAvgExpr,
'_itop_min_' => $oMinExpr,
'_itop_max_' => $oMaxExpr,
);
}
$sSQL = $oSearch->MakeGroupByQuery($aArgs, $aGroupBy, false, $aFunctions, $aOrderBy, $iLimit, $iLimitStart);
$aRes = CMDBSource::QueryToArray($sSQL);
// Display results
if (!empty($aRes))
{
$oP->add('<div>');
$oP->add('<table class="listResults">');
$aLine = $aRes[0];
$aCols = array();
$oP->add('<tr>');
foreach(array_keys($aLine) as $item)
{
if (!is_numeric($item))
{
$aCols[] = $item;
$oP->add("<th>$item</th>");
}
}
$oP->add('</tr>');
foreach($aRes as $aLine)
{
$oP->add('<tr>');
foreach($aCols as $sCol)
{
$oP->add("<td>".$aLine[$sCol]."</td>");
}
$oP->add('</tr>');
}
$oP->add('</table>');
$oP->add('</div>');
}
else
{
$oP->add("<p>No Result</p>\n");
}
}
catch (Exception $e)
{
$oP->p('<div class="header_message message_error">'.$e->getMessage().'</div>');
$bError = true;
}
$oP->add("<div class=\"header_message message_info\">$sSQL</div>\n");
$oP->output();
return;
/*
echo "<pre>";
$aClassSelection = MetaModel::GetClasses();
foreach($aClassSelection as $sClass)
{
if (!MetaModel::HasTable($sClass))
{
continue;
}
foreach(MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef)
{
// Skip this attribute if not defined in this table
if (!MetaModel::IsAttributeOrigin($sClass, $sAttCode))
{
continue;
}
switch (get_class($oAttDef))
{
case 'Integer':
case 'AttributeDecimal':
case 'AttributeDuration':
case 'AttributeSubItem':
case 'AttributePercentage':
echo "$sClass:$sAttCode = ".get_class($oAttDef)."\n";
break;
}
}
}
*/

View File

@@ -0,0 +1 @@
Tests in this folder have been written before the introduction of PHPUnit in iTop, they are not run by the CI.

View File

@@ -0,0 +1,286 @@
<?php
/**
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
/**
* Copyright (C) 2013-2021 Combodo SARL
*
* This file is part of iTop.
*
* iTop is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* iTop is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
*/
require_once('../../../approot.inc.php');
require_once(APPROOT.'/application/application.inc.php');
require_once(APPROOT.'/application/startup.inc.php');
require_once(APPROOT.'/application/loginwebpage.class.inc.php');
LoginWebPage::DoLogin(); // Check user rights and prompt if needed
ApplicationMenu::CheckMenuIdEnabled('RunQueriesMenu');
function ShowExamples($oP, $sExpression)
{
$bUsingExample = false;
$aExamples = array(
'Pedagogic examples' => array(
"Web applications" => "SELECT WebApplication",
"Person having an 'A' in their name" => "SELECT Person AS B WHERE B.name LIKE '%A%'",
"Servers having a name like dbserver1.demo.com or dbserver023.foo.fr" => "SELECT Server WHERE name REGEXP '^dbserver[0-9]+\\\\..+\\\\.[a-z]{2,3}$'",
"Changes planned on new year's day" => "SELECT Change AS ch WHERE ch.start_date >= '2009-12-31' AND ch.end_date <= '2010-01-01'",
"IPs in a range" => "SELECT DatacenterDevice AS dev WHERE INET_ATON(dev.managementip) > INET_ATON('10.22.32.224') AND INET_ATON(dev.managementip) < INET_ATON('10.22.32.255')",
"Persons below a given root organization" => "SELECT Person AS P JOIN Organization AS Node ON P.org_id = Node.id JOIN Organization AS Root ON Node.parent_id BELOW Root.id WHERE Root.id=1",
),
'Usefull examples' => array(
"NW interfaces of equipment in production for customer 'Demo'" => "SELECT PhysicalInterface AS if JOIN DatacenterDevice AS dev ON if.connectableci_id = dev.id WHERE dev.status = 'production' AND dev.organization_name = 'Demo'",
"My tickets" => "SELECT Ticket AS t WHERE t.agent_id = :current_contact_id",
"People being owner of an active ticket" => "SELECT Person AS p JOIN UserRequest AS u ON u.agent_id = p.id WHERE u.status != 'closed'",
"Contracts terminating in the next thirty days" => "SELECT Contract AS c WHERE c.end_date > NOW() AND c.end_date < DATE_ADD(NOW(), INTERVAL 30 DAY)",
"Orphan tickets (opened one hour ago, still not assigned)" => "SELECT UserRequest AS u WHERE u.start_date < DATE_SUB(NOW(), INTERVAL 60 MINUTE) AND u.status = 'new'",
"Long lasting incidents (duration > 8 hours)" => "SELECT UserRequest AS u WHERE u.close_date > DATE_ADD(u.start_date, INTERVAL 8 HOUR)",
),
);
$aDisplayData = array();
$oAppContext = new ApplicationContext();
$sContext = $oAppContext->GetForForm();
foreach ($aExamples as $sTopic => $aQueries)
{
foreach ($aQueries as $sDescription => $sOql)
{
$sHighlight = '';
$sDisable = '';
if ($sOql == $sExpression)
{
// this one is currently being tested, highlight it
$sHighlight = "background-color:yellow;";
$sDisable = 'disabled';
// and remember we are testing a query of the list
$bUsingExample = true;
}
//$aDisplayData[$sTopic][] = array(
$aDisplayData[Dict::S('UI:RunQuery:QueryExamples')][] = array(
'desc' => "<div style=\"$sHighlight\">".htmlentities($sDescription, ENT_QUOTES, 'UTF-8')."</div>",
'oql' => "<div style=\"$sHighlight\">".htmlentities($sOql, ENT_QUOTES, 'UTF-8')."</div>",
'go' => "<form method=\"get\"><input type=\"hidden\" name=\"expression\" value=\"$sOql\"><input type=\"submit\" value=\"".Dict::S('UI:Button:Test')."\" $sDisable>$sContext</form>\n",
);
}
}
$aDisplayConfig = array();
$aDisplayConfig['desc'] = array('label' => Dict::S('UI:RunQuery:HeaderPurpose'), 'description' => Dict::S('UI:RunQuery:HeaderPurpose+'));
$aDisplayConfig['oql'] = array('label' => Dict::S('UI:RunQuery:HeaderOQLExpression'), 'description' => Dict::S('UI:RunQuery:HeaderOQLExpression+'));
$aDisplayConfig['go'] = array('label' => '', 'description' => '');
foreach ($aDisplayData as $sTopic => $aQueriesDisplayData)
{
$bShowOpened = $bUsingExample;
$oP->StartCollapsibleSection($sTopic, $bShowOpened);
$oP->table($aDisplayConfig, $aQueriesDisplayData);
$oP->EndCollapsibleSection();
}
}
$sOperation = utils::ReadParam('operation', 'menu');
$oAppContext = new ApplicationContext();
$oP = new iTopWebPage(Dict::S('UI:RunQuery:Title'));
$oP->SetBreadCrumbEntry('ui-tool-runquery', Dict::S('Menu:RunQueriesMenu'), Dict::S('Menu:RunQueriesMenu+'), '', 'fas fa-terminal', iTopWebPage::ENUM_BREADCRUMB_ENTRY_ICON_TYPE_CSS_CLASSES);
// Main program
$sExpression = utils::ReadParam('expression', '', false, 'raw_data');
$sEncoding = utils::ReadParam('encoding', 'oql');
ShowExamples($oP, $sExpression);
try
{
if ($sEncoding == 'crypted')
{
// Translate $sExpression into a oql expression
$sClearText = base64_decode($sExpression);
echo "<strong>FYI: '$sClearText'</strong><br/>\n";
$oFilter = DBObjectSearch::unserialize($sExpression);
$sExpression = $oFilter->ToOQL();
}
$oFilter = null;
$aArgs = array();
$sSyntaxError = null;
if (!empty($sExpression))
{
try
{
$oFilter = DBObjectSearch::FromOQL($sExpression);
} catch (Exception $e)
{
if ($e instanceof OqlException)
{
$sSyntaxError = $e->getHtmlDesc();
}
else
{
$sSyntaxError = $e->getMessage();
}
}
if ($oFilter)
{
$aArgs = array();
foreach ($oFilter->GetQueryParams() as $sParam => $foo)
{
$value = utils::ReadParam('arg_'.$sParam, null, true, 'raw_data');
if (!is_null($value))
{
$aArgs[$sParam] = $value;
}
else
{
$aArgs[$sParam] = '';
}
}
$oFilter->SetInternalParams($aArgs);
}
}
$oP->add("<form method=\"post\">\n");
$oP->add(Dict::S('UI:RunQuery:ExpressionToEvaluate')."<br/>\n");
$oP->add("<textarea cols=\"120\" rows=\"8\" id=\"expression\" name=\"expression\">".htmlentities($sExpression, ENT_QUOTES, 'UTF-8')."</textarea>\n");
$oP->add_linked_script(utils::GetAbsoluteUrlAppRoot()."/js/jquery.hotkeys.js");
$oP->add_ready_script(<<<EOF
$("#expression").select();
$("#expression").on("keydown", null, "ctrl+return", function() {
$(this).closest("form").submit();
});
EOF
);
if (count($aArgs) > 0)
{
$oP->add("<div class=\"wizContainer\">\n");
$oP->add("<h3>Query arguments</h3>\n");
foreach ($aArgs as $sParam => $sValue)
{
$oP->p("$sParam: <input type=\"string\" name=\"arg_$sParam\" value=\"$sValue\">\n");
}
$oP->add("</div>\n");
}
$oP->add("<input type=\"submit\" value=\"".Dict::S('UI:Button:Evaluate')."\" title=\"".Dict::S('UI:Button:Evaluate:Title')."\">\n");
$oP->add($oAppContext->GetForForm());
$oP->add("</form>\n");
if ($oFilter)
{
$oP->add("<h3>Query results</h3>\n");
$oResultBlock = new DisplayBlock($oFilter, 'list', false);
$oResultBlock->Display($oP, 'runquery');
// Breadcrumb
//$iCount = $oResultBlock->GetDisplayedCount();
$sPageId = "ui-search-".$oFilter->GetClass();
$sLabel = MetaModel::GetName($oFilter->GetClass());
$aArgs = array();
foreach (array_merge($_POST, $_GET) as $sKey => $value)
{
if (is_array($value))
{
$aItems = array();
foreach ($value as $sItemKey => $sItemValue)
{
$aArgs[] = $sKey.'['.$sItemKey.']='.urlencode($sItemValue);
}
}
else
{
$aArgs[] = $sKey.'='.urlencode($value);
}
}
$sUrl = utils::GetAbsoluteUrlAppRoot().'pages/run_query.php?'.implode('&', $aArgs);
$oP->SetBreadCrumbEntry($sPageId, $sLabel, $oFilter->ToOQL(true), $sUrl, 'fas fa-terminal', iTopWebPage::ENUM_BREADCRUMB_ENTRY_ICON_TYPE_CSS_CLASSES);
$oP->p('');
$oP->StartCollapsibleSection(Dict::S('UI:RunQuery:MoreInfo'), false, 'runQuery');
$oP->p('<pre>'.$oFilter->ToOQL().'</pre>');
$oP->EndCollapsibleSection();
$aModifierProperties = MetaModel::MakeModifierProperties($oFilter);
$oP->StartCollapsibleSection('OQL Developed');
$oSQLObjectQueryBuilder = new SQLObjectQueryBuilder($oFilter);
$oBuild = new QueryBuilderContext($oFilter, $aModifierProperties);
$oP->p('<pre>'.$oSQLObjectQueryBuilder->DebugOQLClassTree($oBuild).'</pre>');
$oP->EndCollapsibleSection();
$oP->StartCollapsibleSection('SQL Count');
$oSQLObjectQueryBuilder = new SQLObjectQueryBuilder($oFilter);
$oBuild = new QueryBuilderContext($oFilter, $aModifierProperties);
$oSQLQueryCount = $oSQLObjectQueryBuilder->BuildSQLQueryStruct(null, true, $aModifierProperties);
$oP->p('<pre>'.$oSQLQueryCount->RenderSelect(array(), array(), 0, 0, true, true).'</pre>');
$oP->EndCollapsibleSection();
$oP->StartCollapsibleSection('SQL');
$oSQLObjectQueryBuilder = new SQLObjectQueryBuilder($oFilter);
$oBuild = new QueryBuilderContext($oFilter, $aModifierProperties);
$oSQLQuery = $oSQLObjectQueryBuilder->BuildSQLQueryStruct(null, false, $aModifierProperties);
$oP->p('<pre>'.$oSQLQuery->RenderSelect(array(), array(), 10, 0, false, true).'</pre>');
$oP->EndCollapsibleSection();
}
elseif ($sSyntaxError)
{
if ($e instanceof OqlException)
{
$sWrongWord = $e->GetWrongWord();
$aSuggestedWords = $e->GetSuggestions();
if (count($aSuggestedWords) > 0)
{
$sSuggestedWord = OqlException::FindClosestString($sWrongWord, $aSuggestedWords);
if (strlen($sSuggestedWord) > 0)
{
$oP->p('<b>'.Dict::Format('UI:RunQuery:Error', $e->GetIssue().' <em>'.$sWrongWord).'</em></b>');
$sBefore = substr($sExpression, 0, $e->GetColumn());
$sAfter = substr($sExpression, $e->GetColumn() + strlen($sWrongWord));
$sFixedExpression = $sBefore.$sSuggestedWord.$sAfter;
$sFixedExpressionHtml = $sBefore.'<span style="background-color:yellow">'.$sSuggestedWord.'</span>'.$sAfter;
$oP->p("Suggesting: $sFixedExpressionHtml");
$oP->add('<button onClick="$(\'textarea[name=expression]\').val(\''.htmlentities(addslashes($sFixedExpression)).'\');">Use this query</button>');
}
else
{
$oP->p('<b>'.Dict::Format('UI:RunQuery:Error', $e->getHtmlDesc()).'</b>');
}
}
else
{
$oP->p('<b>'.Dict::Format('UI:RunQuery:Error', $e->getHtmlDesc()).'</b>');
}
}
else
{
$oP->p('<b>'.Dict::Format('UI:RunQuery:Error', $e->getMessage()).'</b>');
}
}
} catch (Exception $e)
{
$oP->p('<b>'.Dict::Format('UI:RunQuery:Error', $e->getMessage()).'</b>');
}
$oP->output();

View File

@@ -0,0 +1,862 @@
<?php
/**
* Copyright (C) 2013-2021 Combodo SARL
*
* This file is part of iTop.
*
* iTop is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* iTop is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
*/
require_once('../../../approot.inc.php');
require_once(APPROOT.'/application/application.inc.php');
require_once(APPROOT.'/application/wizardhelper.class.inc.php');
require_once(APPROOT.'/application/startup.inc.php');
require_once(APPROOT.'/application/loginwebpage.class.inc.php');
require_once(APPROOT.'/application/utils.inc.php');
require_once(APPROOT.'/setup/setuppage.class.inc.php');
//ini_set('memory_limit', '2048M');
class BenchmarkDataCreation
{
var $m_iIfByServer;
var $m_iIfByNWDevice;
var $m_aRequested;
var $m_aPlanned;
var $m_aCreatedByClass = array();
var $m_aCreatedByDesc = array();
var $m_aStatsByClass = array();
/** @var \CMDBChange $m_oChange */
var $m_oChange;
public function __construct()
{
CMDBObject::SetTrackInfo('Benchmark setup');
}
public function PlanStructure($iPlannedContacts, $iPlannedContracts)
{
$this->m_aRequested = array(
'plannedcontacts' => $iPlannedContacts,
'plannedcontracts' => $iPlannedContracts,
);
$this->m_aPlanned = array(
'Contacts' => $iPlannedContacts,
'Contracts' => $iPlannedContracts,
'Documents' => $iPlannedContracts * 2,
);
}
public function PlanCis($iPlannedCIs)
{
$this->m_aRequested = array(
'plannedcis' => $iPlannedCIs,
);
$this->m_iIfByServer = 2;
$this->m_iIfByNWDevice = 10;
$iServers = ceil($iPlannedCIs * 9 / 10);
$iNWDevices = ceil($iPlannedCIs / 10);
$iInterfaces = $iServers * $this->m_iIfByServer + $iNWDevices * $this->m_iIfByNWDevice;
$iApplications = $iServers * 5;
$iSolutions = ceil($iApplications / 2);
$iProcesses = ceil($iSolutions / 2);
$this->m_aPlanned = array(
'Network devices' => $iNWDevices,
'Servers' => $iServers,
'Interfaces' => $iInterfaces,
'Application SW' => 2,
'Applications' => $iApplications,
'Solutions' => $iSolutions,
'Processes' => $iProcesses,
);
}
public function PlanTickets($iPlannedTickets, $iBigTicketCis)
{
$this->m_aRequested = array(
'plannedtickets' => $iPlannedTickets,
'plannedbigticketcis' => $iBigTicketCis,
);
$this->m_aPlanned = array(
'Incidents' => ceil($iPlannedTickets / 2),
'Changes' => ceil($iPlannedTickets / 2),
'Big ticket: CIs' => $iBigTicketCis,
);
}
public function ShowPlans($oP)
{
$oP->add("<h2>Planned creations</h2>\n");
$aPlanned = $this->m_aPlanned;
$aForm = array();
foreach ($aPlanned as $sKey => $iCount)
{
$aForm[] = array(
'label' => $sKey,
'input' => $iCount,
);
}
$oP->form($aForm);
}
public function ShowForm($oP, $sNextOperation)
{
$aRequested = $this->m_aRequested;
$oP->add("<form method=\"post\" onSubmit=\"return DoSubmit('Loading data...', 10)\">\n");
$oP->add("<input type=\"hidden\" name=\"operation\" value=\"$sNextOperation\">\n");
foreach($this->m_aRequested as $sName => $sValue)
{
$oP->add("<input type=\"hidden\" name=\"$sName\" value=\"$sValue\">\n");
}
$oP->add("<button type=\"submit\">Next >></button>\n");
$oP->add("</form>\n");
}
protected function CreateObject($sClass, $aData, $sClassDesc = '')
{
$mu_t1 = MyHelpers::getmicrotime();
$oMyObject = MetaModel::NewObject($sClass);
foreach($aData as $sProp => $value)
{
if (is_array($value))
{
// transform into a link set
$sCSVSpec = implode('|', $value);
$oAttDef = MetaModel::GetAttributeDef($sClass, $sProp);
$value = $oAttDef->MakeValueFromString($sCSVSpec, $bLocalizedValue = false, $sSepItem = '|', $sSepAttribute = ';', $sSepValue = ':', $sAttributeQualifier = '"');
}
$oMyObject->Set($sProp, $value);
}
$iId = $oMyObject->DBInsertNoReload();
$sClassId = "$sClass ($sClassDesc)";
$this->m_aCreatedByDesc[$sClassId][] = $iId;
$this->m_aCreatedByClass[$sClass][] = $iId;
$mu_t2 = MyHelpers::getmicrotime();
$this->m_aStatsByClass[$sClass][] = $mu_t2 - $mu_t1;
return $iId;
}
static $m_aClassIdCache = array();
protected function GetClassIds($sClass)
{
if (!isset(self::$m_aClassIdCache[$sClass]))
{
// Load the cache now
self::$m_aClassIdCache[$sClass] = array();
$oSet = new DBObjectSet(new DBObjectSearch($sClass));
while($oObj = $oSet->Fetch())
{
self::$m_aClassIdCache[$sClass][] = $oObj->GetKey();
}
}
return self::$m_aClassIdCache[$sClass];
}
protected function RandomId($sClass, $sClassDesc = '')
{
$sClassId = "$sClass ($sClassDesc)";
if (isset($this->m_aCreatedByDesc[$sClassId]))
{
return $this->m_aCreatedByDesc[$sClassId][array_rand($this->m_aCreatedByDesc[$sClassId])];
}
$aIds = self::GetClassIds($sClass);
return $aIds[array_rand($aIds)];
}
static protected function FindId($sClass)
{
$oSet = new DBObjectSet(new DBObjectSearch($sClass));
if ($oSet->Count() < 1)
{
return null;
}
$oObj = $oSet->Fetch();
return $oObj->GetKey();
}
static protected function FindIdFromOQL($sOQL)
{
$oSet = new DBObjectSet(DBObjectSearch::FromOQL($sOQL));
if ($oSet->Count() < 1)
{
return null;
}
$oObj = $oSet->Fetch();
return $oObj->GetKey();
}
protected function my_array_rand($aData, $iCount)
{
if ($iCount == 0)
{
return array();
}
elseif ($iCount == 1)
{
// array_rand() for one item returns only the key
$key = array_rand($aData);
$aSample = array($key);
}
elseif ($iCount <= count($aData))
{
$aSample = array_rand($aData, $iCount);
}
else
{
$aSample = array_merge(array_keys($aData), self::my_array_rand($aData, $iCount - count($aData)));
}
return $aSample;
}
protected function CreateLinks($iFrom, $iCount, $sLinkClass, $sAttCodeFrom, $sAttCodeTo)
{
$oAttTo = MetaModel::GetAttributeDef($sLinkClass, $sAttCodeTo);
$sToClass = $oAttTo->GetTargetClass();
$aTargets = self::GetClassIds($sToClass);
$aSample = self::my_array_rand($aTargets, $iCount);
foreach($aSample as $key)
{
$aData = array(
$sAttCodeFrom => $iFrom,
$sAttCodeTo => $aTargets[$key],
);
$this->CreateObject($sLinkClass, $aData);
}
}
public function CreateStructure($oP)
{
$aClasses = MetaModel::GetClasses();
$aActions = array('Read', 'Bulk Read', 'Delete', 'Bulk Delete', 'Modify', 'Bulk Modify');
$aStdProfiles = array(2, 3, 4, 5, 6, 7, 8, 9);
////////////////////////////////////////
// New specific profile, giving access to everything
//
$aData = array(
'name' => 'Data guru',
'description' => 'Could do anything, because everything is granted',
);
$iGuruProfile = $this->CreateObject('URP_Profiles', $aData);
foreach($aClasses as $sClass)
{
foreach($aActions as $sAction)
{
$aData = array(
'profileid' => $iGuruProfile,
'class' => $sClass,
'permission' => 'yes',
'action' => $sAction,
);
$this->CreateObject('URP_ActionGrant', $aData);
}
}
// User login with super access rights
//
$aData = array(
'org_id' => self::FindId('Organization'),
'location_id' => self::FindId('Location'),
'first_name' => 'Jesus',
'name' => 'Deus',
'email' => 'guru@combodo.com',
);
$iPerson = $this->CreateObject('Person', $aData);
$aData = array(
'contactid' => $iPerson,
'login' => 'guru',
'password' => 'guru',
'language' => 'EN US',
'profile_list' => array("profileid:$iGuruProfile;reason:he is the one"),
);
$iLogin = $this->CreateObject('UserLocal', $aData);
////////////////////////////////////////
// User login having all std profiles
//
$aData = array(
'org_id' => self::FindId('Organization'),
'location_id' => self::FindId('Location'),
'first_name' => 'Little ze',
'name' => 'Foo',
'email' => 'foo@combodo.com',
);
$iPerson = $this->CreateObject('Person', $aData);
$aProfileSet = array();
foreach($aStdProfiles as $iProfileId)
{
$aProfileSet[] = "profileid:$iProfileId;reason:xxx";
}
$aData = array(
'contactid' => $iPerson,
'login' => 'foo',
'password' => 'foo',
'language' => 'EN US',
'profile_list' => $aProfileSet,
);
$iLogin = $this->CreateObject('UserLocal', $aData);
/////////////////////////
//
// Organizations
//
$aData = array(
'name' => 'Benchmark',
);
$iOrg = $this->CreateObject('Organization', $aData);
/////////////////////////
//
// Locations
//
$aData = array(
'org_id' => $iOrg,
'name' => 'Rio de Janeiro',
);
$iLoc = $this->CreateObject('Location', $aData);
/////////////////////////
//
// Teams
//
$aData = array(
'org_id' => $iOrg,
'location_id' => $iLoc,
'name' => 'Fluminense',
'email' => 'fluminense@combodo.com',
);
$iTeam = $this->CreateObject('Team', $aData);
/////////////////////////
//
// Persons
//
for($i = 0 ; $i < $this->m_aPlanned['Contacts'] ; $i++)
{
$aData = array(
'org_id' => $iOrg,
'location_id' => $iLoc,
'first_name' => 'Joaõ',
'name' => 'Ningem #'.$i,
'email' => 'foo'.$i.'@nowhere.fr',
);
$iPerson = $this->CreateObject('Person', $aData);
// Contract/Infra
//
$aData = array(
'contact_id' => $iPerson,
'team_id' => $this->RandomId('Team'),
);
$this->CreateObject('lnkTeamToContact', $aData);
}
/////////////////////////
//
// Services
//
$aData = array(
'org_id' => $iOrg,
'name' => 'My Service',
);
$iService = $this->CreateObject('Service', $aData);
/////////////////////////
//
// Service subcategories
//
$aData = array(
'name' => 'My subcategory',
'service_id' => $iService,
);
$iOrg = $this->CreateObject('ServiceSubcategory', $aData);
/////////////////////////
//
// Contracts
//
for($i = 0 ; $i < $this->m_aPlanned['Contracts'] ; $i++)
{
$aData = array(
'name' => "Contract #$i",
'description' => 'Created for benchmarking purposes',
'org_id' => $this->RandomId('Organization'),
'provider_id' => $this->RandomId('Organization'),
'start_date' => '2009-12-25',
'end_date' => '2019-08-01',
'support_team_id' => $this->RandomId('Team'),
);
$iContract = $this->CreateObject('CustomerContract', $aData);
// Contract/Contact (10% of contacts)
//
$iContactCount = ceil($this->m_aPlanned['Contracts'] / 10);
for($iLinked = 0 ; $iLinked < $iContactCount ; $iLinked++)
{
$aData = array(
'contact_id' => $this->RandomId('Person'),
'contract_id' => $iContract,
'role' => 'role '.$iLinked,
);
$this->CreateObject('lnkContractToContact', $aData);
}
}
/////////////////////////
//
// Documents
//
$sMyDoc = '';
for($i = 0 ; $i < 1000 ; $i++)
{
// 100 chars
$sMyDoc .= "012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678\n";
}
$oRefDoc = new ormDocument($sMyDoc, 'text/plain');
for($i = 0 ; $i < $this->m_aPlanned['Documents'] ; $i++)
{
$aData = array(
'org_id' => $iOrg,
'name' => "document$i",
'contents' => $oRefDoc,
);
$this->CreateObject('FileDoc', $aData);
}
}
public function CreateCis($oP)
{
$iOrg = $this->FindIdFromOQL("SELECT Organization WHERE name = 'Benchmark'");
$iLoc = $this->FindIdFromOQL("SELECT Location WHERE org_id = $iOrg");
/////////////////////////
//
// Servers
//
for($i = 0 ; $i < $this->m_aPlanned['Servers'] ; $i++)
{
$aData = array(
'org_id' => $iOrg,
'location_id' => $iLoc,
'name' => 'server'.$i,
'status' => 'production',
);
$iServer = $this->CreateObject('Server', $aData);
// Contract/Infra
$this->CreateLinks($iServer, 1, 'lnkContractToCI', 'ci_id', 'contract_id');
// Interfaces
for($iLinked = 0 ; $iLinked < $this->m_iIfByServer ; $iLinked++)
{
$aData = array(
'name' => "eth$iLinked",
'status' => 'implementation',
'org_id' => $iOrg,
'device_id' => $iServer,
'status' => 'production',
);
$this->CreateObject('NetworkInterface', $aData, 'server if');
}
}
/////////////////////////
//
// Network devices
//
for($i = 0 ; $i < $this->m_aPlanned['Network devices'] ; $i++)
{
$aData = array(
'org_id' => $iOrg,
'location_id' => $iLoc,
'name' => 'equipment #'.$i,
'status' => 'production',
);
$iNWDevice = $this->CreateObject('NetworkDevice', $aData);
// Contract/Infra
$this->CreateLinks($iNWDevice, 1, 'lnkContractToCI', 'ci_id', 'contract_id');
// Interfaces
//
for($iLinked = 0 ; $iLinked < $this->m_iIfByNWDevice ; $iLinked++)
{
$aData = array(
'name' => "eth$iLinked",
'status' => 'implementation',
'org_id' => $iOrg,
'device_id' => $iNWDevice,
'connected_if' => $this->RandomId('NetworkInterface', 'server if'),
'status' => 'production',
);
$this->CreateObject('NetworkInterface', $aData, 'equipment if');
}
}
/////////////////////////
//
// Application Software
//
for($i = 0 ; $i < $this->m_aPlanned['Application SW'] ; $i++)
{
$aData = array(
'name' => 'Software #'.$i,
);
$iNWDevice = $this->CreateObject('Application', $aData);
}
/////////////////////////
//
// Applications
//
for($i = 0 ; $i < $this->m_aPlanned['Applications'] ; $i++)
{
$aData = array(
'org_id' => $iOrg,
'device_id' => $this->RandomId('Server'),
'software_id' => $this->RandomId('Application'),
'name' => 'Application #'.$i,
'status' => 'production',
);
$iAppInstance = $this->CreateObject('ApplicationInstance', $aData);
// Contract/Infra
$this->CreateLinks($iAppInstance, 1, 'lnkContractToCI', 'ci_id', 'contract_id');
}
/////////////////////////
//
// Application Solution
//
for($i = 0 ; $i < $this->m_aPlanned['Solutions'] ; $i++)
{
$aData = array(
'org_id' => $iOrg,
'name' => 'Solution #'.$i,
'status' => 'production',
);
$iAppSolution = $this->CreateObject('ApplicationSolution', $aData);
// Contract/Infra
$this->CreateLinks($iAppSolution, 1, 'lnkContractToCI', 'ci_id', 'contract_id');
}
/////////////////////////
//
// Business Process
//
for($i = 0 ; $i < $this->m_aPlanned['Processes'] ; $i++)
{
$aData = array(
'org_id' => $iOrg,
'name' => 'Process #'.$i,
'status' => 'production',
);
$iProcess = $this->CreateObject('BusinessProcess', $aData);
// Contract/Infra
$this->CreateLinks($iProcess, 1, 'lnkContractToCI', 'ci_id', 'contract_id');
}
}
public function CreateTickets($oP)
{
$iOrg = $this->FindIdFromOQL("SELECT Organization WHERE name = 'Benchmark'");
$iLoc = $this->FindIdFromOQL("SELECT Location WHERE org_id = $iOrg");
/////////////////////////
//
// Incident Tickets
//
for($i = 0 ; $i < $this->m_aPlanned['Incidents'] ; $i++)
{
$aData = array(
'org_id' => $iOrg,
'caller_id' => $this->RandomId('Person'),
'workgroup_id' => $this->RandomId('Team'),
'agent_id' => $this->RandomId('Person'),
'service_id' => $this->RandomId('Service'),
'servicesubcategory_id' => $this->RandomId('ServiceSubcategory'),
'title' => 'Incident #'.$i,
'description' => 'O que aconteceu?',
'ticket_log' => 'Testing...',
);
$iTicket = $this->CreateObject('Incident', $aData);
// Incident/Infra
$iInfraCount = rand(1, 6);
$this->CreateLinks($iTicket, $iInfraCount, 'lnkTicketToCI', 'ticket_id', 'ci_id');
// Incident/Infra
$iContactCount = rand(1, 6);
$this->CreateLinks($iTicket, $iContactCount, 'lnkTicketToContact', 'ticket_id', 'contact_id');
}
/////////////////////////
//
// Big Ticket
//
$aData = array(
'org_id' => $iOrg,
'caller_id' => $this->RandomId('Person'),
'workgroup_id' => $this->RandomId('Team'),
'agent_id' => $this->RandomId('Person'),
'service_id' => $this->RandomId('Service'),
'servicesubcategory_id' => $this->RandomId('ServiceSubcategory'),
'title' => 'Big ticket',
'description' => 'O que aconteceu?',
'ticket_log' => 'Testing...',
);
$iTicket = $this->CreateObject('Incident', $aData);
// Incident/Infra
$iInfraCount = $this->m_aPlanned['Big ticket: CIs'];
$this->CreateLinks($iTicket, $iInfraCount, 'lnkTicketToCI', 'ticket_id', 'ci_id');
// Incident/Infra
$iContactCount = rand(1, 6);
$this->CreateLinks($iTicket, $iContactCount, 'lnkTicketToContact', 'ticket_id', 'contact_id');
/////////////////////////
//
// Change Tickets
//
for($i = 0 ; $i < $this->m_aPlanned['Changes'] ; $i++)
{
$aData = array(
'org_id' => $iOrg,
'requestor_id' => $this->RandomId('Person'),
'workgroup_id' => $this->RandomId('Team'),
'agent_id' => $this->RandomId('Person'),
'supervisor_group_id' => $this->RandomId('Team'),
'supervisor_id' => $this->RandomId('Person'),
'manager_group_id' => $this->RandomId('Team'),
'manager_id' => $this->RandomId('Person'),
'title' => 'change #'.$i,
'description' => "Let's do something there",
);
$iTicket = $this->CreateObject('NormalChange', $aData);
// Incident/Infra
$iInfraCount = rand(1, 6);
$this->CreateLinks($iTicket, $iInfraCount, 'lnkTicketToCI', 'ticket_id', 'ci_id');
// Incident/Infra
$iContactCount = rand(1, 6);
$this->CreateLinks($iTicket, $iContactCount, 'lnkTicketToContact', 'ticket_id', 'contact_id');
}
}
public function MakeFeedback($oP)
{
foreach($this->m_aCreatedByClass as $sClass => $aClassIds)
{
$iSample = reset($aClassIds);
$sSample = "<a href=\"".utils::GetAbsoluteUrlAppRoot()."pages/UI.php?operation=details&class=$sClass&id=$iSample\">sample</a>";
$iDuration = number_format(array_sum($this->m_aStatsByClass[$sClass]), 3);
$fDurationMin = number_format(min($this->m_aStatsByClass[$sClass]), 3);
$fDurationMax = number_format(max($this->m_aStatsByClass[$sClass]), 3);
$fDurationAverage = number_format(array_sum($this->m_aStatsByClass[$sClass]) / count($this->m_aStatsByClass[$sClass]), 3);
$oP->add("<ul>");
$oP->add("<li>");
$oP->add("$sClass: ".count($this->m_aStatsByClass[$sClass])." - $sSample<br/>");
$oP->add("Duration: $fDurationMin =&gt; $fDurationMax; Avg:$fDurationAverage; Total: $iDuration");
$oP->add("</li>");
$oP->add("</ul>");
}
}
}
/**
* Ask the user what are the settings for the data load
*/
function DisplayStep1(SetupPage $oP)
{
$sNextOperation = 'step2';
$oP->add("<h1>iTop benchmarking</h1>\n");
$oP->add("<form method=\"post\" onSubmit=\"return DoSubmit('Evaluating real plans...', 10)\">\n");
$oP->add("<fieldset><legend>Data load configuration</legend>\n");
$aForm = array();
$aForm[] = array(
'label' => "Contacts:",
'input' => "<input id=\"from\" type=\"text\" name=\"plannedcontacts\" value=\"100\">",
'help' => '',
);
$aForm[] = array(
'label' => "Contracts:",
'input' => "<input id=\"from\" type=\"text\" name=\"plannedcontracts\" value=\"10\">",
'help' => '',
);
$oP->form($aForm);
$oP->add("</fieldset>\n");
$oP->add("<input type=\"hidden\" name=\"operation\" value=\"create_structure\">\n");
$oP->add("<button type=\"submit\">Next >></button>\n");
$oP->add("</form>\n");
$oP->add("<form method=\"post\" onSubmit=\"return DoSubmit('Evaluating real plans...', 10)\">\n");
$oP->add("<fieldset><legend>Data load configuration</legend>\n");
$aForm = array();
$aForm[] = array(
'label' => "Main CIs:",
'input' => "<input id=\"to\" type=\"text\" name=\"plannedcis\" value=\"70\">",
'help' => ' exclude interfaces, subnets or any other type of secondary CI',
);
$oP->form($aForm);
$oP->add("</fieldset>\n");
$oP->add("<input type=\"hidden\" name=\"operation\" value=\"create_cis\">\n");
$oP->add("<button type=\"submit\">Next >></button>\n");
$oP->add("</form>\n");
$oP->add("<form method=\"post\" onSubmit=\"return DoSubmit('Evaluating real plans...', 10)\">\n");
$oP->add("<fieldset><legend>Data load configuration</legend>\n");
$aForm = array();
$aForm[] = array(
'label' => "Tickets:",
'input' => "<input id=\"to\" type=\"text\" name=\"plannedtickets\" value=\"200\">",
'help' => ' 50% incidents, 50% changes',
);
$aForm[] = array(
'label' => "CIs for the big ticket:",
'input' => "<input id=\"to\" type=\"text\" name=\"plannedbigticketcis\" value=\"200\">",
'help' => 'Number of CI for the single big ticket',
);
$oP->form($aForm);
$oP->add("</fieldset>\n");
$oP->add("<input type=\"hidden\" name=\"operation\" value=\"create_tickets\">\n");
$oP->add("<button type=\"submit\">Next >></button>\n");
$oP->add("</form>\n");
}
/**
* Main program
*/
LoginWebPage::DoLogin(); // Check user rights and prompt if needed
$sOperation = Utils::ReadParam('operation', 'step1');
$oP = new SetupPage('iTop benchmark utility');
ExecutionKPI::EnableDuration();
$oKPI = new ExecutionKPI();
try
{
switch($sOperation)
{
case 'step1':
DisplayStep1($oP);
break;
case 'create_structure':
$oP->no_cache();
$oP->add_xframe_options('DENY');
$iPlannedContacts = Utils::ReadParam('plannedcontacts');
$iPlannedContracts = Utils::ReadParam('plannedcontracts');
$oDataCreation = new BenchmarkDataCreation();
$oDataCreation->PlanStructure($iPlannedContacts, $iPlannedContracts);
$oDataCreation->ShowPlans($oP);
$oDataCreation->ShowForm($oP, 'create_structure_go');
break;
case 'create_structure_go':
$oP->no_cache();
$iPlannedContacts = Utils::ReadParam('plannedcontacts');
$iPlannedContracts = Utils::ReadParam('plannedcontracts');
$oDataCreation = new BenchmarkDataCreation();
$oDataCreation->PlanStructure($iPlannedContacts, $iPlannedContracts);
$oDataCreation->CreateStructure($oP);
$oDataCreation->MakeFeedback($oP);
break;
case 'create_cis':
$oP->no_cache();
$iPlannedCIs = Utils::ReadParam('plannedcis');
$oDataCreation = new BenchmarkDataCreation();
$oDataCreation->PlanCis($iPlannedCIs);
$oDataCreation->ShowPlans($oP);
$oDataCreation->ShowForm($oP, 'create_cis_go');
break;
case 'create_cis_go':
$oP->no_cache();
$iPlannedCIs = Utils::ReadParam('plannedcis');
$oDataCreation = new BenchmarkDataCreation();
$oDataCreation->PlanCis($iPlannedCIs);
$oDataCreation->CreateCis($oP);
$oDataCreation->MakeFeedback($oP);
break;
case 'create_tickets':
$oP->no_cache();
$iPlannedTickets = Utils::ReadParam('plannedtickets');
$iBigTicketCis = Utils::ReadParam('plannedbigticketcis');
$oDataCreation = new BenchmarkDataCreation();
$oDataCreation->PlanTickets($iPlannedTickets, $iBigTicketCis);
$oDataCreation->ShowPlans($oP);
$oDataCreation->ShowForm($oP, 'create_tickets_go');
break;
case 'create_tickets_go':
$oP->no_cache();
$iPlannedTickets = Utils::ReadParam('plannedtickets');
$iBigTicketCis = Utils::ReadParam('plannedbigticketcis');
$oDataCreation = new BenchmarkDataCreation();
$oDataCreation->PlanTickets($iPlannedTickets, $iBigTicketCis);
$oDataCreation->CreateTickets($oP);
$oDataCreation->MakeFeedback($oP);
break;
default:
$oP->error("Error: unsupported operation '$sOperation'");
}
}
catch(ZZException $e)
{
$oP->error("Error: '".$e->getMessage()."'");
}
catch(ZZCoreException $e)
{
$oP->error("Error: '".$e->getHtmlDesc()."'");
}
$oKPI->ComputeAndReport('Total execution');
//DBSearch::RecordQueryTrace();
$oP->output();
?>

View File

@@ -0,0 +1,146 @@
<?php
// Copyright (c) 2010-2021 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
//
/**
* Date: 06/10/2017
*/
require_once('../../../approot.inc.php');
require_once(APPROOT.'application/startup.inc.php');
\LoginWebPage::DoLogin(true);
$sOQLFile = APPROOT.'log/oql_records.txt';
$sTestFile = APPROOT.'tests/core/oql_records.php';
$oTestHandle = @fopen($sTestFile, "w");
@fwrite($oTestHandle, "<?php\n\n");
$aFoundOQLs = array();
$iCount = 0;
$iRead = 0;
$oOQLHandle = @fopen($sOQLFile, "r");
if ($oOQLHandle) {
while (($sBuffer = fgets($oOQLHandle)) !== false) {
$iRead++;
$aRecord = unserialize($sBuffer);
$sOQL = $aRecord['oql'];
$sChecksum = md5($sBuffer);
if (isset($aFoundOQLs[$sChecksum])) { continue; }
$aFoundOQLs[$sChecksum] = true;
$iCount++;
$sOrderBy = ConvertArray($aRecord['order_by']);
$sAttToLoad = ConvertArray($aRecord['att_to_load']);
$iLimitCount = $aRecord['limit_count'];
$iLimitStart = $aRecord['limit_start'];
// $sOQL, $aOrderBy, $aArgs, $aAttToLoad, $aExtendedDataSpec, $iLimitCount, $iLimitStart
$sLine = "\$aData[\"SELECT $iCount\"] = array(\"$sOQL\", $sOrderBy, array(), $sAttToLoad, array(), $iLimitCount, $iLimitStart);\n";
@fwrite($oTestHandle, $sLine);
}
if (!feof($oOQLHandle)) {
echo "Erreur: fgets() a échoué\n";
}
@fclose($oOQLHandle);
}
@fwrite($oTestHandle, "\n");
@fclose($oTestHandle);
echo "File '$sTestFile' generated with $iCount entries (from $iRead captured OQL).\n";
/// Group by
$sOQLFile = APPROOT.'log/oql_group_by_records.txt';
$sTestFile = APPROOT.'tests/core/oql_group_by_records.php';
$oTestHandle = @fopen($sTestFile, "w");
@fwrite($oTestHandle, "<?php\n\n");
$aFoundOQLs = array();
$iCount = 1000;
$iRead = 0;
$oOQLHandle = @fopen($sOQLFile, "r");
if ($oOQLHandle) {
while (($sBuffer = fgets($oOQLHandle)) !== false) {
$iRead++;
$aRecord = unserialize($sBuffer);
$sOQL = $aRecord['oql'];
$sChecksum = md5($sBuffer);
if (isset($aFoundOQLs[$sChecksum])) { continue; }
$aFoundOQLs[$sChecksum] = true;
$iCount++;
$sOrderBy = ConvertArray($aRecord['order_by']);
$sGroupByExpr = ConvertArray($aRecord['group_by_expr']);
$sSelectExpr = ConvertArray($aRecord['select_expr']);
if ($aRecord['exclude_null_values'])
{
$bExcludeNullValues = 'true';
}
else
{
$bExcludeNullValues = 'false';
}
$iLimitCount = $aRecord['limit_count'];
$iLimitStart = $aRecord['limit_start'];
// $sOQL, $aArgs, $aGroupByExpr, $bExcludeNullValues, $aSelectExpr, $aOrderBy, $iLimitCount, $iLimitStart
$sLine = "\$aData[\"SELECT $iCount\"] = array(\"$sOQL\", array(), $sGroupByExpr, $bExcludeNullValues, $sSelectExpr, $sOrderBy, $iLimitCount, $iLimitStart);\n";
@fwrite($oTestHandle, $sLine);
}
if (!feof($oOQLHandle)) {
echo "Erreur: fgets() a échoué\n";
}
@fclose($oOQLHandle);
}
@fwrite($oTestHandle, "\n");
@fclose($oTestHandle);
echo "<br>File '$sTestFile' generated with ".($iCount-1000)." entries (from $iRead captured OQL).\n";
function ConvertArray($aArray)
{
if (is_null($aArray))
{
return 'null';
}
if (empty($aArray))
{
return 'array()';
}
return 'unserialize(\''.str_replace("'", "\\'",serialize($aArray)).'\')';
}

View File

@@ -0,0 +1,38 @@
<?php
//
// phpMyORM configuration file
//
// To be manually edited (or generated by the configuration wizard)
//
// The file is used in MetaModel::LoadConfig() which does all the necessary initialization job
//
$MySettings = array(
'db_host' => 'localhost',
'db_user' => 'root',
'db_pwd' => '',
'db_name' => 'TestFarm',
'db_subname' => '', // use it to differentiate two applications instances running on the same DB
);
// Modules: file names should be specified as a absolute paths
$MyModules = array(
'application' => array (
// '../core/event.class.inc.php',
// '../core/action.class.inc.php',
// '../core/trigger.class.inc.php',
// to be continued...
),
'business' => array (
'../business/test_farm.class.inc.php',
// to be continued...
),
'addons' => array (
//'user rights' => '/addons/userrights/userrightsnull.class.inc.php', // or userrightsmatrix.class.inc.php
// other modules to come later
)
);
?>

View File

@@ -0,0 +1,104 @@
<?php
/**
* Copyright (C) 2013-2021 Combodo SARL
*
* This file is part of iTop.
*
* iTop is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* iTop is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
*/
/**
* Date: 06/10/2017
*/
require_once('../../../approot.inc.php');
require_once(APPROOT.'application/startup.inc.php');
$sEnvironment = MetaModel::GetEnvironmentId();
$aEntries = array();
$aCacheUserData = apc_cache_info_compat();
if (is_array($aCacheUserData) && isset($aCacheUserData['cache_list']))
{
$sPrefix = 'itop-'.$sEnvironment.'-query-cache-';
foreach($aCacheUserData['cache_list'] as $i => $aEntry)
{
$sEntryKey = array_key_exists('info', $aEntry) ? $aEntry['info'] : $aEntry['key'];
if (strpos($sEntryKey, $sPrefix) === 0)
{
$aEntries[] = $sEntryKey;
}
}
}
echo "<pre>";
if (empty($aEntries))
{
echo "No Data";
return;
}
$sKey = $aEntries[0];
$result = apc_fetch($sKey);
if (!is_object($result))
{
return;
}
$oSQLQuery = $result;
echo "NB Tables before;NB Tables after;";
foreach($oSQLQuery->m_aContextData as $sField => $oValue)
{
echo $sField.';';
}
echo "\n";
sort($aEntries);
foreach($aEntries as $sKey)
{
$result = apc_fetch($sKey);
if (is_object($result))
{
$oSQLQuery = $result;
if (isset($oSQLQuery->m_aContextData))
{
echo $oSQLQuery->m_iOriginalTableCount.";".$oSQLQuery->CountTables().';';
foreach($oSQLQuery->m_aContextData as $oValue)
{
if (is_array($oValue))
{
$sVal = json_encode($oValue);
}
else
{
if (empty($oValue))
{
$sVal = '';
}
else
{
$sVal = $oValue;
}
}
echo $sVal.';';
}
echo "\n";
}
}
}
echo "</pre>";

View File

@@ -0,0 +1,354 @@
<?php
/**
* Copyright (C) 2013-2021 Combodo SARL
*
* This file is part of iTop.
*
* iTop is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* iTop is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
*/
function LogResult($sString)
{
file_put_contents(APPROOT.'data/queries.results.log', "\n".$sString, FILE_APPEND);
}
function LogBenchmarkCSV()
{
$aValues = array();
foreach (func_get_args() as $arg)
{
if (is_string($arg))
{
$aValues[] = '"'.str_replace('"', '""', $arg).'"';
}
else
{
$aValues[] = (string) $arg;
}
}
$sLine = implode(';', $aValues); // the preferred for MS Excel
file_put_contents(APPROOT.'data/queries.benchmark.csv', "\n".$sLine, FILE_APPEND);
}
class QueryLogEntry
{
public function __construct($aLogEntryId, $aLogEntryData)
{
$this->aErrors = array();
$this->sSql = '';
$this->MakeDuration = 0;
$this->fExecDuration = 0;
$this->iTableCount = 0;
$this->aRows = array();
$this->sLogId = $aLogEntryId;
$this->sOql = $aLogEntryData['oql'];
$this->sOqlHtml = htmlentities($this->sOql, ENT_QUOTES, 'UTF-8');
$aQueryData = unserialize($aLogEntryData['data']);
$this->oFilter = $aQueryData['filter'];
$this->sClass = $this->oFilter->GetClass();
$this->aArgs = $aQueryData['args'];
$iRepeat = utils::ReadParam('repeat', 3);
if ($aQueryData['type'] == 'select')
{
$this->aOrderBy = $aQueryData['order_by'];
$this->aAttToLoad = $aQueryData['att_to_load'];
$this->aExtendedDataSpec = $aQueryData['extended_data_spec'];
$this->iLimitCount = $aQueryData['limit_count'];
$this->iLimitStart = $aQueryData['limit_start'];
$this->bGetCount = $aQueryData['is_count'];
if ($this->bGetCount)
{
$this->sQueryType = 'COUNT';
$this->sQueryDesc = '';
}
else
{
$this->sQueryType = 'LIST';
$this->sQueryDesc = "limit count: $this->iLimitCount";
$this->sQueryDesc .= "; limit start: $this->iLimitStart";
if (count($this->aOrderBy) > 0)
{
$this->sQueryDesc .= "; order by: ".implode(',', array_keys($this->aOrderBy));
}
if (is_array($this->aAttToLoad))
{
$this->sQueryDesc .= "; attributes: ".implode(',', array_keys($this->aAttToLoad));
}
}
$fRefTime = MyHelpers::getmicrotime();
try
{
for($i = 0 ; $i < $iRepeat ; $i++)
{
$this->sSql = $this->oFilter->MakeSelectQuery($this->aOrderBy, $this->aArgs, $this->aAttToLoad, $this->aExtendedDataSpec, $this->iLimitCount, $this->iLimitStart, $this->bGetCount);
}
}
catch(Exception $e)
{
$this->aErrors[] = "Failed to create the SQL:".$e->getMessage();
}
$this->fMakeDuration = (MyHelpers::getmicrotime() - $fRefTime) / $iRepeat;
}
elseif ($aQueryData['type'] == 'group_by')
{
$this->aGroupByExpr = $aQueryData['group_by_expr'];
$this->sQueryType = 'GROUP BY';
$aGroupedBy = array();
foreach ($this->aGroupByExpr as $oExpr)
{
$aGroupedBy[] = $oExpr->Render();
}
$this->sQueryDesc = implode(', ', $aGroupedBy);
$fRefTime = MyHelpers::getmicrotime();
try
{
for($i = 0 ; $i < $iRepeat ; $i++)
{
$this->sSql = $this->oFilter->MakeGroupByQuery($this->aArgs, $this->aGroupByExpr);
}
}
catch(Exception $e)
{
$this->aErrors[] = "Failed to create the SQL:".$e->getMessage();
}
$this->fMakeDuration = (MyHelpers::getmicrotime() - $fRefTime) / $iRepeat;
}
else
{
// unsupported
$this->sQueryType = 'ERROR';
$this->sQueryDesc = "Unkown type of query: ".$aQueryData['type'];
}
}
public function Exec()
{
if ($this->sSql != '')
{
$iRepeat = utils::ReadParam('repeat', 3);
try
{
$resQuery = null;
$fRefTime = MyHelpers::getmicrotime();
for($i = 0 ; $i < $iRepeat ; $i++)
{
$resQuery = CMDBSource::Query($this->sSql);
}
$this->fExecDuration = (MyHelpers::getmicrotime() - $fRefTime) / $iRepeat;
// This is not relevant...
if (preg_match_all('|\s*JOIN\s*\(\s*`|', $this->sSql, $aMatches)) // JOIN (`mytable...
{
$this->iTableCount = 1 + count($aMatches[0]);
}
else
{
$this->iTableCount = 1;
}
}
catch (Exception $e)
{
$this->aErrors[] = "Failed to execute the SQL:".$e->getMessage();
}
if ($resQuery)
{
while ($aRow = CMDBSource::FetchArray($resQuery))
{
$this->aRows[] = $aRow;
}
CMDBSource::FreeResult($resQuery);
}
}
}
public function HasErrors()
{
return (count($this->aErrors) > 0);
}
public function Display($oP)
{
$oP->p($this->sOqlHtml);
$oP->p($this->sQueryType);
$oP->p($this->sQueryDesc);
foreach ($this->aErrors as $sError)
{
$oP->p($sError);
}
}
}
/////////////////////////////////////////////////////////////////////////////
//
// Main program
//
/////////////////////////////////////////////////////////////////////////////
require_once('../../../approot.inc.php');
require_once(APPROOT.'/application/application.inc.php');
require_once(APPROOT.'/application/ajaxwebpage.class.inc.php');
require_once(APPROOT.'/application/startup.inc.php');
$operation = utils::ReadParam('operation', '');
require_once(APPROOT.'/application/loginwebpage.class.inc.php');
LoginWebPage::DoLogin(); // Check user rights and prompt if needed
$oP = new WebPage('Replay queries.log');
ini_set('memory_limit', '512M');
require_once(APPROOT.'/data/queries.log');
$iCount = count($aQueriesLog);
$oP->p("Nombre de requêtes: ".$iCount);
$sOperation = utils::ReadParam('operation', '');
switch ($sOperation)
{
case '':
default:
$oP->add("<ol>\n");
foreach ($aQueriesLog as $sQueryId => $aOqlData)
{
$sOql = $aOqlData['oql'];
$sOqlHtml = htmlentities($sOql, ENT_QUOTES, 'UTF-8');
$oP->add("<li>$sOqlHtml <a href=\"?operation=zoom&query=$sQueryId\">zoom</a></li>\n");
}
$oP->add("</ol>\n");
$oP->add("<form action=\"?operation=benchmark&repeat=3\" method=\"post\">\n");
$oP->add("<input type=\"submit\" value=\"Benchmark (3 repeats)!\">\n");
$oP->add("</form>\n");
$oP->add("<form action=\"?operation=check\" method=\"post\">\n");
$oP->add("<input type=\"submit\" value=\"Check!\">\n");
$oP->add("</form>\n");
break;
case 'zoom':
$sQueryId = utils::ReadParam('query', '', false, 'raw_data');
$oP->add("<h2>Zoom on query</h2>\n");
$oQuery = new QueryLogEntry($sQueryId, $aQueriesLog[$sQueryId]);
$oQuery->Exec();
$oQuery->Display($oP);
$oP->add("<pre>$oQuery->sSql</pre>\n");
$oP->p("Tables: $oQuery->iTableCount");
if (strlen($oQuery->sSql) > 0)
{
$aExplain = CMDBSource::ExplainQuery($oQuery->sSql);
$oP->add("<h4>Explain</h4>\n");
$oP->add("<table style=\"border=1px;\">\n");
foreach ($aExplain as $aRow)
{
$oP->add(" <tr>\n");
$oP->add(" <td>".implode('</td><td>', $aRow)."</td>\n");
$oP->add(" </tr>\n");
}
$oP->add("</table>\n");
}
if (count($oQuery->aRows))
{
$oP->add("<h4>Values</h4>\n");
$oP->add("<table style=\"border=1px;\">\n");
foreach ($oQuery->aRows as $iRow => $aRow)
{
$oP->add(" <tr>\n");
$oP->add(" <td>".implode('</td><td>', $aRow)."</td>\n");
$oP->add(" </tr>\n");
}
$oP->add("</table>\n");
}
else
{
$oP->p("No data");
}
break;
case 'check':
$oP->add("<h2>List queries in error</h2>\n");
foreach ($aQueriesLog as $sQueryId => $aOqlData)
{
$oQuery = new QueryLogEntry($sQueryId, $aOqlData);
$oQuery->Exec();
if ($oQuery->HasErrors())
{
$oQuery->Display($oP);
$oP->p("<a href=\"?operation=zoom&query=$sQueryId\">zoom</a>");
}
}
break;
case 'benchmark':
$oP->add("<h2>Create data/queries.xxx reports</h2>\n");
// Reset the log contents
file_put_contents(APPROOT.'data/queries.results.log', date('Y-m-d H:i:s')."\n");
file_put_contents(APPROOT.'data/queries.benchmark.csv', '');
LogBenchmarkCSV('type', 'properties', 'make duration', 'class', 'tables', 'query length', 'exec duration', 'rows', 'oql');
$iErrors = 0;
foreach ($aQueriesLog as $sQueryId => $aOqlData)
{
$oQuery = new QueryLogEntry($sQueryId, $aOqlData);
$oQuery->Exec();
LogResult('-----------------------------------------------------------');
LogResult($oQuery->sOql);
LogResult($oQuery->sQueryType);
if (strlen($oQuery->sQueryDesc) > 0)
{
LogResult($oQuery->sQueryDesc);
}
if ($oQuery->HasErrors())
{
foreach($oQuery->aErrors as $sError)
{
LogResult($sError);
$iErrors++;
}
}
else
{
LogResult("row count = ".count($oQuery->aRows));
foreach($oQuery->aRows as $iRow => $aRow)
{
LogResult("row: ".serialize($aRow));
if ($iRow > 100) break;
}
LogBenchmarkCSV($oQuery->sQueryType, $oQuery->sQueryDesc, sprintf('%1.3f', round($oQuery->fMakeDuration, 3)), $oQuery->sClass, $oQuery->iTableCount, strlen($oQuery->sSql), sprintf('%1.4f', round($oQuery->fExecDuration, 4)), count($oQuery->aRows), $oQuery->sOql);
}
}
}
$oP->output();
?>

View File

@@ -0,0 +1,551 @@
<?php
// Copyright (C) 2010-2021 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
/**
* Core automated tests - basics
*
* @copyright Copyright (C) 2010-2021 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
require_once(APPROOT.'/core/attributedef.class.inc.php');
require_once(APPROOT.'/core/filterdef.class.inc.php');
require_once(APPROOT.'/core/stimulus.class.inc.php');
require_once(APPROOT.'/core/MyHelpers.class.inc.php');
require_once(APPROOT.'/core/oql/expression.class.inc.php');
require_once(APPROOT.'/core/cmdbsource.class.inc.php');
require_once(APPROOT.'/core/sqlquery.class.inc.php');
require_once(APPROOT.'/core/sqlobjectquery.class.inc.php');
require_once(APPROOT.'/core/sqlunionquery.class.inc.php');
require_once(APPROOT.'/core/log.class.inc.php');
require_once(APPROOT.'/core/kpi.class.inc.php');
require_once(APPROOT.'/core/dbobject.class.php');
require_once(APPROOT.'/core/dbsearch.class.php');
require_once(APPROOT.'/core/dbobjectset.class.php');
require_once(APPROOT.'/application/cmdbabstract.class.inc.php');
require_once(APPROOT.'/core/userrights.class.inc.php');
require_once(APPROOT.'/webservices/webservices.class.inc.php');
// Just to differentiate programmatically triggered exceptions and other kind of errors (usefull?)
class UnitTestException extends Exception
{}
/**
* Improved display of the backtrace
*
* @package iTopORM
*/
class ExceptionFromError extends Exception
{
public function getTraceAsHtml()
{
$aBackTrace = $this->getTrace();
return MyHelpers::get_callstack_html(0, $this->getTrace());
// return "<pre>\n".$this->getTraceAsString()."</pre>\n";
}
}
/**
* Test handler API and basic helpers
*
* @package iTopORM
*/
abstract class TestHandler
{
protected $m_aSuccesses;
protected $m_aWarnings;
protected $m_aErrors;
protected $m_sOutput;
public function __construct()
{
$this->m_aSuccesses = array();
$this->m_aWarnings = array();
$this->m_aErrors = array();
}
static public function GetName() {return "fooname";}
static public function GetDescription(){return "foodesc";}
protected function DoPrepare() {return true;}
abstract protected function DoExecute();
protected function DoCleanup() {return true;}
protected static function DumpVariable($var)
{
echo "<pre class=\"vardump\">\n";
print_r($var);
echo "</pre>\n";
}
protected function ReportSuccess($sMessage, $sSubtestId = '')
{
$this->m_aSuccesses[] = $sMessage;
}
protected function ReportWarning($sMessage, $sSubtestId = '')
{
$this->m_aWarnings[] = $sMessage;
}
protected function ReportError($sMessage, $sSubtestId = '')
{
$this->m_aErrors[] = $sMessage;
}
public function GetResults()
{
return $this->m_aSuccesses;
}
public function GetWarnings()
{
return $this->m_aWarnings;
}
public function GetErrors()
{
return $this->m_aErrors;
}
public function GetOutput()
{
return $this->m_sOutput;
}
public function error_handler($errno, $errstr, $errfile, $errline)
{
// Note: return false to call the default handler (stop the program if an error)
if ($errstr == 'assert()') $errno = E_USER_ERROR;
switch ($errno)
{
case E_USER_ERROR:
case E_WARNING: //(assertion failed)
$this->ReportError("$errfile@$errline - $errstr");
break;
case E_USER_WARNING:
$this->ReportWarning("$errfile@$errline - $errstr");
break;
case E_USER_NOTICE:
$this->ReportWarning("$errfile@$errline - $errstr");
break;
default:
$this->ReportWarning("$errfile@$errline - Unknown error type: [$errno] $errstr");
echo "Unknown error type: [$errno] $errstr in $errfile at $errline<br />\n";
break;
}
return true; // do not call the default handler
}
public function Execute()
{
ob_start();
set_error_handler(array($this, 'error_handler'));
try
{
$this->DoPrepare();
$this->DoExecute();
}
catch (ExceptionFromError $e)
{
$this->ReportError($e->getMessage().' - '.$e->getTraceAsHtml());
}
catch (CoreException $e)
{
//$this->ReportError($e->getMessage());
//$this->ReportError($e->__tostring());
$this->ReportError($e->getMessage().' - '.$e->getTraceAsHtml());
}
catch (Exception $e)
{
//$this->ReportError($e->getMessage());
//$this->ReportError($e->__tostring());
$this->ReportError('class '.get_class($e).' --- '.$e->getMessage().' - '.$e->getTraceAsString());
}
restore_error_handler();
$this->m_sOutput = ob_get_clean();
return (count($this->GetErrors()) == 0);
}
static protected function DoPostRequestAuth($sRelativeUrl, $aData, $sLogin = 'admin', $sPassword = 'admin', $sOptionnalHeaders = null)
{
$aDataAndAuth = $aData;
// To be changed to use basic authentication
$aDataAndAuth['operation'] = 'login';
$aDataAndAuth['auth_user'] = $sLogin;
$aDataAndAuth['auth_pwd'] = $sPassword;
$sHost = $_SERVER['HTTP_HOST'];
$sRawPath = $_SERVER['SCRIPT_NAME'];
$sPath = dirname($sRawPath);
$sUrl = "http://$sHost/$sPath/$sRelativeUrl";
return self::DoPostRequest($sUrl, $aDataAndAuth, $sOptionnalHeaders);
}
// Source: http://netevil.org/blog/2006/nov/http-post-from-php-without-curl
// originaly named after do_post_request
// Partially adapted to our coding conventions
static protected function DoPostRequest($sUrl, $aData, $sOptionnalHeaders = null)
{
// $sOptionnalHeaders is a string containing additional HTTP headers that you would like to send in your request.
$sData = http_build_query($aData);
$aParams = array('http' => array(
'method' => 'POST',
'content' => $sData,
'header'=> "Content-type: application/x-www-form-urlencoded\r\nContent-Length: ".strlen($sData)."\r\n",
));
if ($sOptionnalHeaders !== null)
{
$aParams['http']['header'] .= $sOptionnalHeaders;
}
$ctx = stream_context_create($aParams);
$fp = @fopen($sUrl, 'rb', false, $ctx);
if (!$fp)
{
global $php_errormsg;
if (isset($php_errormsg))
{
throw new Exception("Problem with $sUrl, $php_errormsg");
}
else
{
throw new Exception("Problem with $sUrl");
}
}
$response = @stream_get_contents($fp);
if ($response === false)
{
throw new Exception("Problem reading data from $sUrl, $php_errormsg");
}
return $response;
}
}
/**
* Test to execute a piece of code (checks if an error occurs)
*
* @package iTopORM
*/
abstract class TestFunction extends TestHandler
{
// simply overload DoExecute (temporary)
}
/**
* Test to execute a piece of code (checks if an error occurs)
*
* @package iTopORM
*/
abstract class TestWebServices extends TestHandler
{
}
/**
* Test to execute a piece of code (checks if an error occurs)
*
* @package iTopORM
*/
abstract class TestSoapWebService extends TestHandler
{
// simply overload DoExecute (temporary)
function __construct()
{
parent::__construct();
}
}
/**
* Test to check that a function outputs some values depending on its input
*
* @package iTopORM
*/
abstract class TestFunctionInOut extends TestFunction
{
// abstract static public function GetCallSpec(); // parameters to call_user_func
// abstract static public function GetInOut(); // array of input => output
protected function DoExecute()
{
$aTests = $this->GetInOut();
if (is_array($aTests))
{
foreach ($aTests as $iTestId => $aTest)
{
$ret = call_user_func_array($this->GetCallSpec(), $aTest['args']);
if ($ret != $aTest['output'])
{
// Note: to be improved to cope with non string parameters
$this->ReportError("Found '$ret' while expecting '".$aTest['output']."'", $iTestId);
}
else
{
$this->ReportSuccess("Found the expected output '$ret'", $iTestId);
}
}
}
else
{
$ret = call_user_func($this->GetCallSpec());
$this->ReportSuccess('Finished successfully');
}
}
}
/**
* Test to check an URL (Searches for Error/Warning/Etc keywords)
*
* @package iTopORM
*/
abstract class TestUrl extends TestHandler
{
// abstract static public function GetUrl();
// abstract static public function GetErrorKeywords();
// abstract static public function GetWarningKeywords();
protected function DoExecute()
{
return true;
}
}
/**
* Test to check a user management module
*
* @package iTopORM
*/
abstract class TestUserRights extends TestHandler
{
protected function DoExecute()
{
return true;
}
}
/**
* Test to execute a scenario on a given DB
*
* @package iTopORM
*/
abstract class TestScenarioOnDB extends TestHandler
{
// abstract static public function GetDBHost();
// abstract static public function GetDBUser();
// abstract static public function GetDBPwd();
// abstract static public function GetDBName();
protected function DoPrepare()
{
$sDBHost = $this->GetDBHost();
$sDBUser = $this->GetDBUser();
$sDBPwd = $this->GetDBPwd();
$sDBName = $this->GetDBName();
CMDBSource::Init($sDBHost, $sDBUser, $sDBPwd);
CMDBSource::SetCharacterSet();
if (CMDBSource::IsDB($sDBName))
{
CMDBSource::DropDB($sDBName);
}
CMDBSource::CreateDB($sDBName);
}
protected function DoCleanup()
{
// CMDBSource::DropDB($this->GetDBName());
}
}
/**
* Test to use a business model on a given DB
*
* @package iTopORM
*/
abstract class TestBizModel extends TestHandler
{
// abstract static public function GetDBSubName();
// abstract static public function GetBusinessModelFile();
// abstract static public function GetConfigFile();
static public function GetConfigFile() {return 'conf/production/config-itop.php';}
protected function DoPrepare()
{
$sConfigFile = APPROOT.$this->GetConfigFile();
MetaModel::Startup($sConfigFile);
// #@# Temporary disabled by Romain
// MetaModel::CheckDefinitions();
// something here to create records... but that's another story
}
protected $m_oChange;
protected function GetCurrentChange()
{
if (!isset($this->m_oChange))
{
new CMDBChange();
$oMyChange = MetaModel::NewObject("CMDBChange");
$oMyChange->Set("date", time());
$oMyChange->Set("userinfo", "Someone doing some tests");
$iChangeId = $oMyChange->DBInsertNoReload();
$this->m_oChange = $oMyChange;
}
return $this->m_oChange;
}
protected function ObjectToDB($oNew, $bReload = false)
{
if ($bReload)
{
$iId = $oNew->DBInsert();
}
else
{
$iId = $oNew->DBInsertNoReload();
}
return $iId;
}
protected function UpdateObjectInDB($oObject)
{
$oObject->DBUpdate();
}
protected function ResetDB()
{
if (MetaModel::DBExists(false))
{
MetaModel::DBDrop();
}
MetaModel::DBCreate();
}
static protected function show_list($oObjectSet)
{
$oObjectSet->Rewind();
$aData = array();
while ($oItem = $oObjectSet->Fetch())
{
$aValues = array();
foreach(MetaModel::GetAttributesList(get_class($oItem)) as $sAttCode)
{
$aValues[$sAttCode] = $oItem->GetAsHTML($sAttCode);
}
//echo $oItem->GetKey()." => ".implode(", ", $aValues)."</br>\n";
$aData[] = $aValues;
}
echo MyHelpers::make_table_from_assoc_array($aData);
}
static protected function search_and_show_list(DBSearch $oMyFilter)
{
$oObjSet = new CMDBObjectSet($oMyFilter);
echo $oMyFilter->ToOQL()."' - Found ".$oObjSet->Count()." items.</br>\n";
self::show_list($oObjSet);
}
static protected function search_and_show_list_from_oql($sOQL)
{
echo $sOQL."...<br/>\n";
$oNewFilter = DBObjectSearch::FromOQL($sOQL);
self::search_and_show_list($oNewFilter);
}
}
/**
* Test to execute a scenario common to any business model (tries to build all the possible queries, etc.)
*
* @package iTopORM
*/
abstract class TestBizModelGeneric extends TestBizModel
{
static public function GetName()
{
return 'Full test on a given business model';
}
static public function GetDescription()
{
return 'Systematic tests: gets each and every existing class and tries every attribute, search filters, etc.';
}
protected function DoPrepare()
{
parent::DoPrepare();
if (!MetaModel::DBExists(false))
{
MetaModel::DBCreate();
}
// something here to create records... but that's another story
}
protected function DoExecute()
{
foreach(MetaModel::GetClasses() as $sClassName)
{
if (MetaModel::HasTable($sClassName)) continue;
$oNobody = MetaModel::GetObject($sClassName, 123);
$oBaby = new $sClassName;
$oFilter = new DBObjectSearch($sClassName);
// Challenge reversibility of OQL / filter object
//
$sExpr1 = $oFilter->ToOQL();
$oNewFilter = DBObjectSearch::FromOQL($sExpr1);
$sExpr2 = $oNewFilter->ToOQL();
if ($sExpr1 != $sExpr2)
{
$this->ReportError("Found two different OQL expression out of the (same?) filter: <em>$sExpr1</em> != <em>$sExpr2</em>");
}
// Use the filter (perform the query)
//
$oSet = new CMDBObjectSet($oFilter);
$this->ReportSuccess('Found '.$oSet->Count()." objects of class $sClassName");
}
return true;
}
}
?>

View File

@@ -0,0 +1,162 @@
<?php
/**
* Copyright (C) 2013-2021 Combodo SARL
*
* This file is part of iTop.
*
* iTop is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* iTop is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
*/
?>
<style>
.vardump {
font-size:8pt;
line-height:100%;
}
</style>
<?php
///////////////////////////////////////////////////////////////////////////////
// Helpers
///////////////////////////////////////////////////////////////////////////////
function ReadMandatoryParam($sName)
{
$value = utils::ReadParam($sName, null);
if (is_null($value))
{
echo "<p>Missing mandatory argument <b>$sName</b></p>";
exit;
}
return $value;
}
function IsAValidTestClass($sClassName)
{
// Must be a child of TestHandler
//
if (!is_subclass_of($sClassName, 'TestHandler')) return false;
// Must not be abstract
//
$oReflectionClass = new ReflectionClass($sClassName);
if (!$oReflectionClass->isInstantiable()) return false;
return true;
}
function GetTestClassLine($sClassName)
{
$oReflectionClass = new ReflectionClass($sClassName);
return $oReflectionClass->getStartLine();
}
function DisplayEvents($aEvents, $sTitle)
{
echo "<h4>$sTitle</h4>\n";
if (count($aEvents) > 0)
{
echo "<ul>\n";
foreach ($aEvents as $sEvent)
{
echo "<li>$sEvent</li>\n";
}
echo "</ul>\n";
}
else
{
echo "<p>none</p>\n";
}
}
///////////////////////////////////////////////////////////////////////////////
// Main
///////////////////////////////////////////////////////////////////////////////
date_default_timezone_set('Europe/Paris');
require_once('../../../approot.inc.php');
require_once(APPROOT.'/application/utils.inc.php');
require_once('./test.class.inc.php');
require_once('./testlist.inc.php');
require_once(APPROOT.'/core/cmdbobject.class.inc.php');
$sTodo = utils::ReadParam("todo", "");
if ($sTodo == '')
{
// Show the list of tests
//
echo "<h3>Existing tests</h3>\n";
echo "<ul>\n";
foreach (get_declared_classes() as $sClassName)
{
if (!IsAValidTestClass($sClassName)) continue;
$sName = call_user_func(array($sClassName, 'GetName'));
$sDescription = call_user_func(array($sClassName, 'GetDescription'));
echo "<li><a href=\"?todo=exec&testid=$sClassName\">$sName</a> ($sDescription)</li>\n";
}
echo "</ul>\n";
}
else if ($sTodo == 'exec')
{
// Execute a test
//
$sTestClass = ReadMandatoryParam("testid");
if (!IsAValidTestClass($sTestClass))
{
echo "<p>Wrong value for testid, expecting a valid class name</p>\n";
}
else
{
$oTest = new $sTestClass();
$iStartLine = GetTestClassLine($sTestClass);
echo "<h3>Testing: ".$oTest->GetName()."</h3>\n";
echo "<h6>testlist.inc.php: $iStartLine</h6>\n";
$bRes = $oTest->Execute();
}
/*
MyHelpers::var_dump_html($oTest->GetResults());
MyHelpers::var_dump_html($oTest->GetWarnings());
MyHelpers::var_dump_html($oTest->GetErrors());
*/
if ($bRes)
{
echo "<p>Success :-)</p>\n";
DisplayEvents($oTest->GetResults(), 'Results');
}
else
{
echo "<p>Failure :-(</p>\n";
}
DisplayEvents($oTest->GetErrors(), 'Errors');
DisplayEvents($oTest->GetWarnings(), 'Warnings');
// Render the output
//
echo "<h4>Actual output</h4>\n";
echo "<div style=\"border: dashed; background-color:light-grey;\">\n";
echo $oTest->GetOutput();
echo "</div>\n";
}
else
{
}
?>

File diff suppressed because it is too large Load Diff