Internal: BulkChange improved with synchro mode

SVN:trunk[1001]
This commit is contained in:
Romain Quetiez
2010-11-30 15:32:30 +00:00
parent bb4c6aecfa
commit 018344578e
4 changed files with 256 additions and 24 deletions

View File

@@ -219,6 +219,14 @@ class RowStatus_Modify extends RowStatus
} }
} }
class RowStatus_Disappeared extends RowStatus_Modify
{
public function GetDescription()
{
return "disappeared, changed ".$this->m_iChanged." cols";
}
}
class RowStatus_Issue extends RowStatus class RowStatus_Issue extends RowStatus
{ {
protected $m_sReason; protected $m_sReason;
@@ -247,15 +255,19 @@ class BulkChange
// #@# todo: rename the variables to sColIndex // #@# todo: rename the variables to sColIndex
protected $m_aAttList; // attcode => iCol protected $m_aAttList; // attcode => iCol
protected $m_aExtKeys; // aExtKeys[sExtKeyAttCode][sExtReconcKeyAttCode] = iCol; protected $m_aExtKeys; // aExtKeys[sExtKeyAttCode][sExtReconcKeyAttCode] = iCol;
protected $m_aReconcilKeys;// attcode (attcode = 'id' for the pkey) protected $m_aReconcilKeys; // attcode (attcode = 'id' for the pkey)
protected $m_sSynchroScope; // OQL - if specified, then the missing items will be reported
protected $m_aOnDisappear; // array of attcode => value, values to be set when an object gets out of scope (ignored if no scope has been defined)
public function __construct($sClass, $aData, $aAttList, $aExtKeys, $aReconcilKeys) public function __construct($sClass, $aData, $aAttList, $aExtKeys, $aReconcilKeys, $sSynchroScope = null, $aOnDisappear = null)
{ {
$this->m_sClass = $sClass; $this->m_sClass = $sClass;
$this->m_aData = $aData; $this->m_aData = $aData;
$this->m_aAttList = $aAttList; $this->m_aAttList = $aAttList;
$this->m_aReconcilKeys = $aReconcilKeys; $this->m_aReconcilKeys = $aReconcilKeys;
$this->m_aExtKeys = $aExtKeys; $this->m_aExtKeys = $aExtKeys;
$this->m_sSynchroScope = $sSynchroScope;
$this->m_aOnDisappear = $aOnDisappear;
} }
protected function ResolveExternalKey($aRowData, $sAttCode, &$aResults) protected function ResolveExternalKey($aRowData, $sAttCode, &$aResults)
@@ -430,6 +442,70 @@ class BulkChange
return $aResults; return $aResults;
} }
protected function PrepareMissingObject(&$oTargetObj, &$aErrors)
{
$aResults = array();
$aErrors = array();
// External keys
//
foreach($this->m_aExtKeys as $sAttCode => $aKeyConfig)
{
//$oExtKey = MetaModel::GetAttributeDef(get_class($oTargetObj), $sAttCode);
$aResults[$sAttCode]= new CellStatus_Void($oTargetObj->Get($sAttCode));
foreach ($aKeyConfig as $sForeignAttCode => $iCol)
{
$aResults[$iCol] = new CellStatus_Void('?');
}
}
// Update attributes
//
foreach($this->m_aOnDisappear as $sAttCode => $value)
{
if (!MetaModel::IsValidAttCode(get_class($oTargetObj), $sAttCode))
{
throw new BulkChangeException('Invalid attribute code', array('class' => get_class($oTargetObj), 'attcode' => $sAttCode));
}
$oTargetObj->Set($sAttCode, $value);
if (!array_key_exists($sAttCode, $this->m_aAttList))
{
// #@# will be out of the reporting... (counted anyway)
}
}
// Reporting on fields
//
$aChangedFields = $oTargetObj->ListChanges();
foreach ($this->m_aAttList as $sAttCode => $iCol)
{
if ($sAttCode == 'id')
{
$aResults[$iCol]= new CellStatus_Void($oTargetObj->GetKey());
}
if (array_key_exists($sAttCode, $aChangedFields))
{
$aResults[$iCol]= new CellStatus_Modify($oTargetObj->Get($sAttCode), $oTargetObj->GetOriginal($sAttCode));
}
else
{
// By default... nothing happens
$aResults[$iCol]= new CellStatus_Void($oTargetObj->Get($sAttCode));
}
}
// Checks
//
$res = $oTargetObj->CheckConsistency();
if ($res !== true)
{
// $res contains the error description
$aErrors["GLOBAL"] = "Attributes not consistent with each others: $res";
}
return $aResults;
}
protected function CreateObject(&$aResult, $iRow, $aRowData, CMDBChange $oChange = null) protected function CreateObject(&$aResult, $iRow, $aRowData, CMDBChange $oChange = null)
{ {
@@ -512,6 +588,40 @@ class BulkChange
$aResult[$iRow]["__STATUS__"] = new RowStatus_NoChange(); $aResult[$iRow]["__STATUS__"] = new RowStatus_NoChange();
} }
} }
protected function UpdateMissingObject(&$aResult, $iRow, $oTargetObj, CMDBChange $oChange = null)
{
$aResult[$iRow] = $this->PrepareMissingObject($oTargetObj, $aErrors);
// Reporting
//
$aResult[$iRow]["finalclass"] = get_class($oTargetObj);
$aResult[$iRow]["id"] = new CellStatus_Void($oTargetObj->GetKey());
if (count($aErrors) > 0)
{
$sErrors = implode(', ', $aErrors);
$aResult[$iRow]["__STATUS__"] = new RowStatus_Issue("Unexpected attribute value(s)");
return;
}
$aChangedFields = $oTargetObj->ListChanges();
if (count($aChangedFields) > 0)
{
$aResult[$iRow]["__STATUS__"] = new RowStatus_Disappeared(count($aChangedFields));
// Optionaly record the results
//
if ($oChange)
{
$oTargetObj->DBUpdateTracked($oChange);
}
}
else
{
$aResult[$iRow]["__STATUS__"] = new RowStatus_Disappeared(0);
}
}
public function Process(CMDBChange $oChange = null) public function Process(CMDBChange $oChange = null)
{ {
@@ -528,14 +638,23 @@ class BulkChange
print_r($this->m_aExtKeys); print_r($this->m_aExtKeys);
echo "Reconciliation:\n"; echo "Reconciliation:\n";
print_r($this->m_aReconcilKeys); print_r($this->m_aReconcilKeys);
echo "Synchro scope:\n";
print_r($this->m_sSynchroScope);
echo "Synchro changes:\n";
print_r($this->m_aOnDisappear);
//echo "Data:\n"; //echo "Data:\n";
//print_r($this->m_aData); //print_r($this->m_aData);
echo "</pre>\n"; echo "</pre>\n";
exit; exit;
} }
// Compute the results // Compute the results
// //
if (!is_null($this->m_sSynchroScope))
{
$aVisited = array();
}
$aResult = array(); $aResult = array();
foreach($this->m_aData as $iRow => $aRowData) foreach($this->m_aData as $iRow => $aRowData)
{ {
@@ -612,6 +731,10 @@ class BulkChange
$oTargetObj = $oReconciliationSet->Fetch(); $oTargetObj = $oReconciliationSet->Fetch();
$this->UpdateObject($aResult, $iRow, $oTargetObj, $aRowData, $oChange); $this->UpdateObject($aResult, $iRow, $oTargetObj, $aRowData, $oChange);
// $aResult[$iRow]["__STATUS__"]=> set in UpdateObject // $aResult[$iRow]["__STATUS__"]=> set in UpdateObject
if (!is_null($this->m_sSynchroScope))
{
$aVisited[] = $oTargetObj->GetKey();
}
break; break;
default: default:
// Found several matches, ambiguous // Found several matches, ambiguous
@@ -645,6 +768,23 @@ class BulkChange
} }
} }
} }
if (!is_null($this->m_sSynchroScope))
{
// Compute the delta between the scope and visited objects
$oScopeSearch = DBObjectSearch::FromOQL($this->m_sSynchroScope);
$oScopeSet = new DBObjectSet($oScopeSearch);
while ($oObj = $oScopeSet->Fetch())
{
$iObj = $oObj->GetKey();
if (!in_array($iObj, $aVisited))
{
$iRow++;
$this->UpdateMissingObject($aResult, $iRow, $oObj, $oChange);
}
}
}
return $aResult; return $aResult;
} }
} }

BIN
images/delete.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 842 B

View File

@@ -327,13 +327,18 @@ try
$oMyChange->Set("userinfo", $sUserString); $oMyChange->Set("userinfo", $sUserString);
$iChangeId = $oMyChange->DBInsert(); $iChangeId = $oMyChange->DBInsert();
} }
$sSynchroScope = null; // e.g. "SELECT Server";
$aSynchroUpdate = null; // e.g. array('status' => 'obsolete')
$oBulk = new BulkChange( $oBulk = new BulkChange(
$sClassName, $sClassName,
$aData, $aData,
$aAttributes, $aAttributes,
$aExtKeys, $aExtKeys,
array_keys($aSearchKeys) array_keys($aSearchKeys),
$sSynchroScope,
$aSynchroUpdate
); );
$oPage->add('<input type="hidden" name="csvdata_truncated" id="csvdata_truncated" value="'.htmlentities($sCSVDataTruncated, ENT_QUOTES, 'UTF-8').'"/>'); $oPage->add('<input type="hidden" name="csvdata_truncated" id="csvdata_truncated" value="'.htmlentities($sCSVDataTruncated, ENT_QUOTES, 'UTF-8').'"/>');
@@ -352,16 +357,15 @@ try
} }
$sHtml .= '<th>Message</th>'; $sHtml .= '<th>Message</th>';
$sHtml .= '</tr>'; $sHtml .= '</tr>';
$iLine = 0;
$iErrors = 0; $iErrors = 0;
$iCreated = 0; $iCreated = 0;
$iModified = 0; $iModified = 0;
$iUnchanged = 0; $iUnchanged = 0;
foreach($aData as $aRow) foreach($aRes as $iLine => $aResRow)
{ {
$oStatus = $aRes[$iLine]['__STATUS__']; $oStatus = $aResRow['__STATUS__'];
$sUrl = ''; $sUrl = '';
$sMessage = ''; $sMessage = '';
$sCSSRowClass = ''; $sCSSRowClass = '';
@@ -370,8 +374,8 @@ try
{ {
case 'RowStatus_NoChange': case 'RowStatus_NoChange':
$iUnchanged++; $iUnchanged++;
$sFinalClass = $aRes[$iLine]['finalclass']; $sFinalClass = $aResRow['finalclass'];
$oObj = MetaModel::GetObject($sFinalClass, $aRes[$iLine]['id']->GetValue()); $oObj = MetaModel::GetObject($sFinalClass, $aResRow['id']->GetValue());
$sUrl = $oObj->GetHyperlink(); $sUrl = $oObj->GetHyperlink();
$sStatus = '<img src="../images/unchanged.png" title="Unchanged">'; $sStatus = '<img src="../images/unchanged.png" title="Unchanged">';
$sCSSRowClass = 'row_unchanged'; $sCSSRowClass = 'row_unchanged';
@@ -379,16 +383,33 @@ try
case 'RowStatus_Modify': case 'RowStatus_Modify':
$iModified++; $iModified++;
$sFinalClass = $aRes[$iLine]['finalclass']; $sFinalClass = $aResRow['finalclass'];
$oObj = MetaModel::GetObject($sFinalClass, $aRes[$iLine]['id']->GetValue()); $oObj = MetaModel::GetObject($sFinalClass, $aResRow['id']->GetValue());
$sUrl = $oObj->GetHyperlink(); $sUrl = $oObj->GetHyperlink();
$sStatus = '<img src="../images/modified.png" title="Modified">'; $sStatus = '<img src="../images/modified.png" title="Modified">';
$sCSSRowClass = 'row_modified'; $sCSSRowClass = 'row_modified';
break; break;
case 'RowStatus_Disappeared':
$iModified++;
$sFinalClass = $aResRow['finalclass'];
$oObj = MetaModel::GetObject($sFinalClass, $aResRow['id']->GetValue());
$sUrl = $oObj->GetHyperlink();
$sStatus = '<img src="../images/delete.png" title="Missing">';
$sCSSRowClass = 'row_modified';
if ($bSimulate)
{
$sMessage = 'Missing object: will be updated';
}
else
{
$sMessage = 'Missing object: updated';
}
break;
case 'RowStatus_NewObj': case 'RowStatus_NewObj':
$iCreated++; $iCreated++;
$sFinalClass = $aRes[$iLine]['finalclass']; $sFinalClass = $aResRow['finalclass'];
$sStatus = '<img src="../images/added.png" title="Created">'; $sStatus = '<img src="../images/added.png" title="Created">';
$sCSSRowClass = 'row_added'; $sCSSRowClass = 'row_added';
if ($bSimulate) if ($bSimulate)
@@ -397,8 +418,8 @@ try
} }
else else
{ {
$sFinalClass = $aRes[$iLine]['finalclass']; $sFinalClass = $aResRow['finalclass'];
$oObj = MetaModel::GetObject($sFinalClass, $aRes[$iLine]['id']->GetValue()); $oObj = MetaModel::GetObject($sFinalClass, $aResRow['id']->GetValue());
$sUrl = $oObj->GetHyperlink(); $sUrl = $oObj->GetHyperlink();
$sMessage = 'Object created'; $sMessage = 'Object created';
} }
@@ -410,7 +431,11 @@ try
$sStatus = '<img src="../images/error.png" title="Error">'; $sStatus = '<img src="../images/error.png" title="Error">';
$sCSSMessageClass = 'cell_error'; $sCSSMessageClass = 'cell_error';
$sCSSRowClass = 'row_error'; $sCSSRowClass = 'row_error';
$aResult[] = $sTextQualifier.implode($sTextQualifier.$sSeparator.$sTextQualifier,$aRow).$sTextQualifier; // Remove the first line and store it in case of error if (array_key_exists($iLine, $aData))
{
$aRow = $aData[$iLine];
$aResult[] = $sTextQualifier.implode($sTextQualifier.$sSeparator.$sTextQualifier,$aRow).$sTextQualifier; // Remove the first line and store it in case of error
}
break; break;
} }
$sHtml .= '<tr class="'.$sCSSRowClass.'">'; $sHtml .= '<tr class="'.$sCSSRowClass.'">';
@@ -421,12 +446,12 @@ try
{ {
if (!empty($sAttCode) && ($sAttCode != ':none:') && ($sAttCode != 'finalclass')) if (!empty($sAttCode) && ($sAttCode != ':none:') && ($sAttCode != 'finalclass'))
{ {
$oCellStatus = $aRes[$iLine][$iNumber -1]; $oCellStatus = $aResRow[$iNumber -1];
$sCellMessage = ''; $sCellMessage = '';
if (isset($aExternalKeysByColumn[$iNumber -1])) if (isset($aExternalKeysByColumn[$iNumber -1]))
{ {
$sExtKeyName = $aExternalKeysByColumn[$iNumber -1]; $sExtKeyName = $aExternalKeysByColumn[$iNumber -1];
$oExtKeyCellStatus = $aRes[$iLine][$sExtKeyName]; $oExtKeyCellStatus = $aResRow[$sExtKeyName];
switch(get_class($oExtKeyCellStatus)) switch(get_class($oExtKeyCellStatus))
{ {
case 'CellStatus_Issue': case 'CellStatus_Issue':
@@ -443,34 +468,34 @@ try
// Do nothing // Do nothing
} }
} }
$sHtmlValue = htmlentities($oCellStatus->GetValue(), ENT_QUOTES, 'UTF-8');
switch(get_class($oCellStatus)) switch(get_class($oCellStatus))
{ {
case 'CellStatus_Issue': case 'CellStatus_Issue':
$sCellMessage .= $oPage->GetP($oCellStatus->GetDescription()); $sCellMessage .= $oPage->GetP($oCellStatus->GetDescription());
$sHtml .= '<td class="cell_error" style="border-right:1px #eee solid;">ERROR: '.htmlentities($aData[$iLine][$iNumber-1], ENT_QUOTES, 'UTF-8').$sCellMessage.'</td>'; $sHtml .= '<td class="cell_error" style="border-right:1px #eee solid;">ERROR: '.$sHtmlValue.$sCellMessage.'</td>';
break; break;
case 'CellStatus_SearchIssue': case 'CellStatus_SearchIssue':
$sCellMessage .= $oPage->GetP($oCellStatus->GetDescription()); $sCellMessage .= $oPage->GetP($oCellStatus->GetDescription());
$sHtml .= '<td class="cell_error">ERROR: '.htmlentities($aData[$iLine][$iNumber-1], ENT_QUOTES, 'UTF-8').$sCellMessage.'</td>'; $sHtml .= '<td class="cell_error">ERROR: '.$sHtmlValue.$sCellMessage.'</td>';
break; break;
case 'CellStatus_Ambiguous': case 'CellStatus_Ambiguous':
$sCellMessage .= $oPage->GetP($oCellStatus->GetDescription()); $sCellMessage .= $oPage->GetP($oCellStatus->GetDescription());
$sHtml .= '<td class="cell_error" style="border-right:1px #eee solid;">AMBIGUOUS: '.htmlentities($aData[$iLine][$iNumber-1], ENT_QUOTES, 'UTF-8').$sCellMessage.'</td>'; $sHtml .= '<td class="cell_error" style="border-right:1px #eee solid;">AMBIGUOUS: '.$sHtmlValue.$sCellMessage.'</td>';
break; break;
case 'CellStatus_Modify': case 'CellStatus_Modify':
$sHtml .= '<td class="cell_modified" style="border-right:1px #eee solid;"><b>'.htmlentities($aData[$iLine][$iNumber-1], ENT_QUOTES, 'UTF-8').'</b></td>'; $sHtml .= '<td class="cell_modified" style="border-right:1px #eee solid;"><b>'.$sHtmlValue.'</b></td>';
break; break;
default: default:
$sHtml .= '<td class="cell_ok" style="border-right:1px #eee solid;">'.htmlentities($aData[$iLine][$iNumber-1], ENT_QUOTES, 'UTF-8').$sCellMessage.'</td>'; $sHtml .= '<td class="cell_ok" style="border-right:1px #eee solid;">'.$sHtmlValue.$sCellMessage.'</td>';
} }
} }
} }
$sHtml .= "<td class=\"$sCSSMessageClass\" style=\"background-color:#f1f1f1;\">$sMessage</td>"; $sHtml .= "<td class=\"$sCSSMessageClass\" style=\"background-color:#f1f1f1;\">$sMessage</td>";
$iLine++;
$sHtml .= '</tr>'; $sHtml .= '</tr>';
} }
$sHtml .= '</table>'; $sHtml .= '</table>';

View File

@@ -987,8 +987,8 @@ class TestBulkChangeOnFarm extends TestBizModel
$oParser = new CSVParser("denomination,hauteur,age $oParser = new CSVParser("denomination,hauteur,age
suzy,123,2009-01-01 suzy,123,2009-01-01
chita,456, chita,456,
"); ", ',', '"');
$aData = $oParser->ToArray(array('_name', '_height', '_birth'), ','); $aData = $oParser->ToArray(1, array('_name', '_height', '_birth'));
self::DumpVariable($aData); self::DumpVariable($aData);
$oBulk = new BulkChange( $oBulk = new BulkChange(
@@ -1193,6 +1193,73 @@ class TestItopEfficiency extends TestBizModel
} }
} }
///////////////////////////////////////////////////////////////////////////
// Test bulk load API
///////////////////////////////////////////////////////////////////////////
class TestItopBulkLoad extends TestBizModel
{
static public function GetName()
{
return 'Itop - test BulkChange class';
}
static public function GetDescription()
{
return 'Execute a bulk change at the Core API level';
}
static public function GetConfigFile() {return '/config-itop.php';}
protected function DoExecute()
{
$oParser = new CSVParser("name,org_id->name,brand,model
Server1,Demo,,
server4,Demo,,
", ',', '"');
$aData = $oParser->ToArray(1, array('_name', '_org_name', '_brand', '_model'));
self::DumpVariable($aData);
$oBulk = new BulkChange(
'Server',
$aData,
// attributes
array('name' => '_name', 'brand' => '_brand', 'model' => '_model'),
// ext keys
array('org_id' => array('name' => '_org_name')),
// reconciliation
array('name'),
// Synchro - scope
"SELECT Server",
// Synchro - set attribute on missing objects
array ('brand' => 'you let package', 'model' => 'tpe', 'cpu' => 'it is pay you')
);
if (false)
{
$oMyChange = MetaModel::NewObject("CMDBChange");
$oMyChange->Set("date", time());
$oMyChange->Set("userinfo", "Testor");
$iChangeId = $oMyChange->DBInsert();
// echo "Created new change: $iChangeId</br>";
}
echo "<h3>Planned for loading...</h3>";
$aRes = $oBulk->Process();
self::DumpVariable($aRes);
if (false)
{
echo "<h3>Go for loading...</h3>";
$aRes = $oBulk->Process($oMyChange);
self::DumpVariable($aRes);
}
return;
}
}
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
// Test data load // Test data load
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////