Преглед на файлове

Implementing Trac #419: provide means for the toolkit to fix synchro data sources

git-svn-id: http://svn.code.sf.net/p/itop/code/trunk@1452 a333f486-631f-4898-b8df-5754b55c2be0
dflaven преди 14 години
родител
ревизия
65ee993dc7
променени са 2 файла, в които са добавени 234 реда и са изтрити 45 реда
  1. 64 12
      core/metamodel.class.php
  2. 170 33
      synchro/synchrodatasource.class.inc.php

+ 64 - 12
core/metamodel.class.php

@@ -2609,10 +2609,13 @@ if (!array_key_exists($sAttCode, self::$m_aAttribDefs[$sClass]))
 
 
 	/**
 	/**
 	 * Check (and updates if needed) the hierarchical keys
 	 * Check (and updates if needed) the hierarchical keys
+	 * @param $bDiagnosticsOnly boolean If true only a diagnostic pass will be run, returning true or false
+	 * @param $bVerbose boolean Displays some information about what is done/what needs to be done	 
 	 * @param $bForceComputation boolean If true, the _left and _right parameters will be recomputed even if some values already exist in the DB	 
 	 * @param $bForceComputation boolean If true, the _left and _right parameters will be recomputed even if some values already exist in the DB	 
 	 */	 	
 	 */	 	
-	public static function CheckHKeys($bForceComputation = false)
+	public static function CheckHKeys($bDiagnosticsOnly = false, $bVerbose = false, $bForceComputation = false)
 	{
 	{
+		$bChangeNeeded = false;
 		foreach (self::GetClasses() as $sClass)
 		foreach (self::GetClasses() as $sClass)
 		{
 		{
 			if (!self::HasTable($sClass)) continue;
 			if (!self::HasTable($sClass)) continue;
@@ -2622,10 +2625,20 @@ if (!array_key_exists($sAttCode, self::$m_aAttribDefs[$sClass]))
 				// Check (once) all the attributes that are hierarchical keys
 				// Check (once) all the attributes that are hierarchical keys
 				if((self::GetAttributeOrigin($sClass, $sAttCode) == $sClass) && $oAttDef->IsHierarchicalKey())
 				if((self::GetAttributeOrigin($sClass, $sAttCode) == $sClass) && $oAttDef->IsHierarchicalKey())
 				{
 				{
-					self::HKInit($sClass, $sAttCode, $bForceComputation);
+					if ($bVerbose)
+					{
+						echo "The attribute $sAttCode from $sClass is a hierarchical key.\n";				
+					}
+					$bResult = self::HKInit($sClass, $sAttCode, $bDiagnosticsOnly, $bVerbose, $bForceComputation);
+					$bChangeNeeded |= $bResult;
+					if ($bVerbose && !$bResult)
+					{
+						echo "Ok, the attribute $sAttCode from class $sClass seems up to date.\n";				
+					}
 				}
 				}
 			}
 			}
 		}
 		}
+		return $bChangeNeeded;
 	}
 	}
 
 
 	/**
 	/**
@@ -2634,9 +2647,12 @@ if (!array_key_exists($sAttCode, self::$m_aAttribDefs[$sClass]))
 	 * to correspond to the existing hierarchy in the database
 	 * to correspond to the existing hierarchy in the database
 	 * @param $sClass string Name of the class to process
 	 * @param $sClass string Name of the class to process
 	 * @param $sAttCode string Code of the attribute to process
 	 * @param $sAttCode string Code of the attribute to process
-	 * @param $bForceComputation boolean If true, the _left and _right parameters will be recomputed even if some values already exist in the DB	 
+	 * @param $bDiagnosticsOnly boolean If true only a diagnostic pass will be run, returning true or false
+	 * @param $bVerbose boolean Displays some information about what is done/what needs to be done	 
+	 * @param $bForceComputation boolean If true, the _left and _right parameters will be recomputed even if some values already exist in the DB
+	 * @return true if an update is needed (diagnostics only) / was performed	 
 	 */
 	 */
-	public static function HKInit($sClass, $sAttCode, $bForceComputation = false)
+	public static function HKInit($sClass, $sAttCode, $bDiagnosticsOnly = false, $bVerbose = false, $bForceComputation = false)
 	{
 	{
 		$idx = 1;
 		$idx = 1;
 		$bUpdateNeeded = $bForceComputation;
 		$bUpdateNeeded = $bForceComputation;
@@ -2644,24 +2660,35 @@ if (!array_key_exists($sAttCode, self::$m_aAttribDefs[$sClass]))
 		$sTable = self::DBGetTable($sClass, $sAttCode);
 		$sTable = self::DBGetTable($sClass, $sAttCode);
 		if ($oAttDef->IsHierarchicalKey())
 		if ($oAttDef->IsHierarchicalKey())
 		{
 		{
-			if (!$bForceComputation)
+			// Check if some values already exist in the table for the _right value, if so, do nothing
+			$sRight = $oAttDef->GetSQLRight();
+			$sSQL = "SELECT MAX(`$sRight`) AS MaxRight FROM `$sTable`";
+			$iMaxRight = CMDBSource::QueryToScalar($sSQL);
+			$sSQL = "SELECT COUNT(`$sRight`) AS Count FROM `$sTable`";
+			$iCount = CMDBSource::QueryToScalar($sSQL);
+			if (!$bForceComputation && ($iCount != 0) && ($iMaxRight == 0))
 			{
 			{
-				// Check if some values already exist in the table for the _right value, if so, do nothing
-				$sRight = $oAttDef->GetSQLRight();
-				$sSQL = "SELECT MAX(`$sRight`) AS MaxRight FROM `$sTable`";
-				$iMaxRight = CMDBSource::QueryToScalar($sSQL);
-				if ($iMaxRight == 0)
+				$bUpdateNeeded = true;
+				if ($bVerbose)
 				{
 				{
-					$bUpdateNeeded = true;
+					echo "The table '$sTable' must be updated to compute the fields $sRight and ".$oAttDef->GetSQLLeft()."\n";
 				}
 				}
 			}
 			}
-			if ($bUpdateNeeded)
+			if ($bForceComputation && !$bDiagnosticsOnly)
+			{
+				echo "Rebuilding the fields $sRight and ".$oAttDef->GetSQLLeft()." from table '$sTable'...\n";
+			}
+			if ($bUpdateNeeded && !$bDiagnosticsOnly)
 			{
 			{
 				try
 				try
 				{
 				{
 					CMDBSource::Query('START TRANSACTION');
 					CMDBSource::Query('START TRANSACTION');
 					self::HKInitChildren($sTable, $sAttCode, $oAttDef, 0, $idx);
 					self::HKInitChildren($sTable, $sAttCode, $oAttDef, 0, $idx);
 					CMDBSource::Query('COMMIT');
 					CMDBSource::Query('COMMIT');
+					if ($bVerbose)
+					{
+						echo "Ok, table '$sTable' successfully updated.\n";
+					}
 				}
 				}
 				catch(Exception $e)
 				catch(Exception $e)
 				{
 				{
@@ -2670,6 +2697,7 @@ if (!array_key_exists($sAttCode, self::$m_aAttribDefs[$sClass]))
 				}
 				}
 			}
 			}
 		}
 		}
+		return $bUpdateNeeded;
 	}
 	}
 	
 	
 	/**
 	/**
@@ -2693,6 +2721,30 @@ if (!array_key_exists($sAttCode, self::$m_aAttribDefs[$sClass]))
 		}
 		}
 	}
 	}
 	
 	
+	public static function CheckDataSources($bDiagnostics, $bVerbose)
+	{
+		$sOQL = 'SELECT SynchroDataSource';
+		$oSet = new DBObjectSet(DBObjectSearch::FromOQL($sOQL));
+		$bFixNeeded = false;
+		if ($bVerbose && $oSet->Count() == 0)
+		{
+			echo "There are no Data Sources in the database.\n";
+		}
+		while($oSource = $oSet->Fetch())
+		{
+			if ($bVerbose)
+			{
+				echo "Checking Data Source '".$oSource->GetName()."'...\n";
+				$bFixNeeded = $bFixNeeded | $oSource->CheckDBConsistency($bDiagnostics, $bVerbose);
+			}
+		}
+		if (!$bFixNeeded && $bVerbose)
+		{
+			echo "Ok.\n";
+		}
+		return $bFixNeeded;	
+	}
+	
 	public static function GenerateUniqueAlias(&$aAliases, $sNewName, $sRealName)
 	public static function GenerateUniqueAlias(&$aAliases, $sNewName, $sRealName)
 	{
 	{
 		if (!array_key_exists($sNewName, $aAliases))
 		if (!array_key_exists($sNewName, $aAliases))

+ 170 - 33
synchro/synchrodatasource.class.inc.php

@@ -476,27 +476,7 @@ EOF
 		{
 		{
 			if(!isset($aAttributes[$sAttCode]))
 			if(!isset($aAttributes[$sAttCode]))
 			{
 			{
-				$oAttDef = MetaModel::GetAttributeDef($this->GetTargetClass(), $sAttCode);
-				if ($oAttDef->IsExternalKey())
-				{
-					$oAttribute = new SynchroAttExtKey();
-					$oAttribute->Set('reconciliation_attcode', ''); // Blank means by pkey
-				}
-				elseif ($oAttDef->IsLinkSet() && $oAttDef->IsIndirect())
-				{
-					$oAttribute = new SynchroAttLinkSet();
-					// Todo - set those value from the form
-					$oAttribute->Set('row_separator', MetaModel::GetConfig()->Get('link_set_item_separator'));
-					$oAttribute->Set('attribute_separator', MetaModel::GetConfig()->Get('link_set_attribute_separator'));
-					$oAttribute->Set('value_separator', MetaModel::GetConfig()->Get('link_set_value_separator'));
-					$oAttribute->Set('attribute_qualifier', MetaModel::GetConfig()->Get('link_set_attribute_qualifier'));
-				}
-				else
-				{
-					$oAttribute = new SynchroAttribute();
-				}
-				$oAttribute->Set('sync_source_id', $this->GetKey());
-				$oAttribute->Set('attcode', $sAttCode);
+				$oAttribute = $this->CreateSynchroAtt($sAttCode);
 			}
 			}
 			else
 			else
 			{
 			{
@@ -527,9 +507,40 @@ EOF
 		$this->Set('attribute_list', $oAttributeSet);
 		$this->Set('attribute_list', $oAttributeSet);
 	}
 	}
 	
 	
-	/*
-	* Overload the standard behavior
-	*/	
+	/**
+	 * Creates a new SynchroAttXXX object in memory with the default values
+	 */
+	protected function CreateSynchroAtt($sAttCode)
+	{
+		$oAttDef = MetaModel::GetAttributeDef($this->GetTargetClass(), $sAttCode);
+		if ($oAttDef->IsExternalKey())
+		{
+			$oAttribute = new SynchroAttExtKey();
+			$oAttribute->Set('reconciliation_attcode', ''); // Blank means by pkey
+		}
+		elseif ($oAttDef->IsLinkSet() && $oAttDef->IsIndirect())
+		{
+			$oAttribute = new SynchroAttLinkSet();
+			// Todo - set those value from the form
+			$oAttribute->Set('row_separator', MetaModel::GetConfig()->Get('link_set_item_separator'));
+			$oAttribute->Set('attribute_separator', MetaModel::GetConfig()->Get('link_set_attribute_separator'));
+			$oAttribute->Set('value_separator', MetaModel::GetConfig()->Get('link_set_value_separator'));
+			$oAttribute->Set('attribute_qualifier', MetaModel::GetConfig()->Get('link_set_attribute_qualifier'));
+		}
+		else
+		{
+			$oAttribute = new SynchroAttribute();
+		}
+		$oAttribute->Set('sync_source_id', $this->GetKey());
+		$oAttribute->Set('attcode', $sAttCode);
+		$oAttribute->Set('reconcile', 0);
+		$oAttribute->Set('update', 0);
+		$oAttribute->Set('update_policy', 'master_locked');
+		return $oAttribute;
+	}
+	/**
+	 * Overload the standard behavior
+	 */	
 	public function ComputeValues()
 	public function ComputeValues()
 	{
 	{
 		parent::ComputeValues();
 		parent::ComputeValues();
@@ -667,13 +678,31 @@ EOF
 		$sCreateTable = "CREATE TABLE `$sTable` ($sFieldDefs) ENGINE = innodb;";
 		$sCreateTable = "CREATE TABLE `$sTable` ($sFieldDefs) ENGINE = innodb;";
 		CMDBSource::Query($sCreateTable);
 		CMDBSource::Query($sCreateTable);
 
 
+		$aTriggers = $this->GetTriggersDefinition();
+		foreach($aTriggers as $key => $sTriggerSQL)
+		{
+			CMDBSource::Query($sTriggerSQL);
+		}
+	}
+
+	/**
+	 * Gets the definitions of the 3 triggers: before insert, before update and after delete
+	 * @return array An array with 3 entries, one for each of the SQL queries
+	 */
+	protected function GetTriggersDefinition()
+	{
+		$sTable = $this->GetDataTable();
+		$sReplicaTable = MetaModel::DBGetTable('SynchroReplica');
+		$aColumns = $this->GetSQLColumns();
+		$aResult = array();
+
 		$sTriggerInsert = "CREATE TRIGGER `{$sTable}_bi` BEFORE INSERT ON $sTable";
 		$sTriggerInsert = "CREATE TRIGGER `{$sTable}_bi` BEFORE INSERT ON $sTable";
 		$sTriggerInsert .= "   FOR EACH ROW";
 		$sTriggerInsert .= "   FOR EACH ROW";
 		$sTriggerInsert .= "   BEGIN";
 		$sTriggerInsert .= "   BEGIN";
 		$sTriggerInsert .= "      INSERT INTO {$sReplicaTable} (sync_source_id, status_last_seen, `status`) VALUES ({$this->GetKey()}, NOW(), 'new');";
 		$sTriggerInsert .= "      INSERT INTO {$sReplicaTable} (sync_source_id, status_last_seen, `status`) VALUES ({$this->GetKey()}, NOW(), 'new');";
 		$sTriggerInsert .= "      SET NEW.id = LAST_INSERT_ID();";
 		$sTriggerInsert .= "      SET NEW.id = LAST_INSERT_ID();";
 		$sTriggerInsert .= "   END;";
 		$sTriggerInsert .= "   END;";
-		CMDBSource::Query($sTriggerInsert);
+		$aResult['bi'] = $sTriggerInsert;
 
 
 		$aModified = array();
 		$aModified = array();
 		foreach($aColumns as $sColumn => $ColSpec)
 		foreach($aColumns as $sColumn => $ColSpec)
@@ -696,14 +725,15 @@ EOF
 		$sTriggerUpdate .= "         SET NEW.id = OLD.id;"; // make sure this id won't change
 		$sTriggerUpdate .= "         SET NEW.id = OLD.id;"; // make sure this id won't change
 		$sTriggerUpdate .= "      END IF;";
 		$sTriggerUpdate .= "      END IF;";
 		$sTriggerUpdate .= "   END;";
 		$sTriggerUpdate .= "   END;";
-		CMDBSource::Query($sTriggerUpdate);
-
-		$sTriggerInsert = "CREATE TRIGGER `{$sTable}_ad` AFTER DELETE ON $sTable";
-		$sTriggerInsert .= "   FOR EACH ROW";
-		$sTriggerInsert .= "   BEGIN";
-		$sTriggerInsert .= "      DELETE FROM {$sReplicaTable} WHERE id = OLD.id;";
-		$sTriggerInsert .= "   END;";
-		CMDBSource::Query($sTriggerInsert);
+		$aResult['bu'] = $sTriggerUpdate;
+
+		$sTriggerDelete = "CREATE TRIGGER `{$sTable}_ad` AFTER DELETE ON $sTable";
+		$sTriggerDelete .= "   FOR EACH ROW";
+		$sTriggerDelete .= "   BEGIN";
+		$sTriggerDelete .= "      DELETE FROM {$sReplicaTable} WHERE id = OLD.id;";
+		$sTriggerDelete .= "   END;";
+		$aResult['ad'] = $sTriggerDelete;
+		return $aResult;
 	}
 	}
 	
 	
 	protected function AfterDelete()
 	protected function AfterDelete()
@@ -717,6 +747,113 @@ EOF
 		// TO DO - check that triggers get dropped with the table
 		// TO DO - check that triggers get dropped with the table
 	}
 	}
 
 
+	/**
+	 * Checks if the data source definition is consistent with the schema of the target class
+	 * @param $bDiagnostics boolean True to only diagnose the consistency, false to actually apply some changes
+	 * @param $bVerbose boolean True to get some information in the std output (echo)
+	 * @return bool Whether or not the database needs fixing for this data source
+	 */
+	public function CheckDBConsistency($bDiagnostics, $bVerbose, $oChange = null)
+	{
+		$bFixNeeded = false;
+		$aMissingFields = array();
+		$oAttributeSet = $this->Get('attribute_list');
+		$aAttributes = array();
+
+		while($oAttribute = $oAttributeSet->Fetch())
+		{
+			$aAttributes[$oAttribute->Get('attcode')] = $oAttribute;
+		}
+
+		foreach(MetaModel::ListAttributeDefs($this->GetTargetClass()) as $sAttCode=>$oAttDef)
+		{
+			if ($oAttDef->IsWritable())
+			{
+				if (!isset($aAttributes[$sAttCode]))
+				{
+					$bFixNeeded = true;
+					$aMissingFields[] = $sAttCode;
+					// New field missing...
+					if ($bDiagnostics)
+					{
+						// Report the issue
+						if ($bVerbose)
+						{
+							echo "Missing field description for the field '$sAttCode', for the data synchro task ".$this->GetName()." (".$this->GetKey()."), will be created with default values.\n";
+						}
+					}
+					else
+					{
+						if ($oChange == null)
+						{
+							$oChange = MetaModel::NewObject("CMDBChange");
+							$oChange->Set("date", time());
+							$sUserString = CMDBChange::GetCurrentUserName();
+							$oChange->Set("userinfo", $sUserString);
+							$oChange->DBInsert();	
+						}
+						// Fix the issue
+						$oAttribute = $this->CreateSynchroAtt($sAttCode);
+						$oAttribute->DBInsertTracked($oChange);
+					}
+				}
+			}
+		}
+		$sTable = $this->GetDataTable();
+		if ($bFixNeeded)
+		{
+			// The structure of the table needs adjusting
+			$aColumns = $this->GetSQLColumns($aMissingFields);
+			$aFieldDefs = array();
+			foreach($aColumns as $sAttCode => $sColumnDef)
+			{
+				$aFieldDefs[] = "$sAttCode $sColumnDef";
+			}
+			$sAlterTable = "ALTER TABLE `$sTable` ADD (".implode(',', $aFieldDefs).");";
+
+			// The triggers as well must be adjusted
+			$aTriggers = array();
+			$aTriggersDefs = $this->GetTriggersDefinition();
+			$aTriggers[] = "DROP TRIGGER IF EXISTS {$sTable}_bi;";
+			$aTriggers[] = $aTriggersDefs['bi'];
+			$aTriggers[] = "DROP TRIGGER IF EXISTS {$sTable}_bu;";
+			$aTriggers[] = $aTriggersDefs['bu'];
+			$aTriggers[] = "DROP TRIGGER IF EXISTS {$sTable}_ad;";
+			$aTriggers[] = $aTriggersDefs['ad'];
+			
+			if ($bDiagnostics)
+			{
+				if  ($bVerbose)
+				{
+					// Report the issue
+					echo "The structure of the table $sTable for the data synchro task ".$this->GetName()." (".$this->GetKey().") must be altered (missing fields: ".implode(',', $aMissingFields).").\n";
+					echo "$sAlterTable\n";
+					echo "The trigger {$sTable}_bi, {$sTable}_bu, {$sTable}_ad for the data synchro task ".$this->GetName()." (".$this->GetKey().") must be re-created.\n";
+					echo implode("\n", $aTriggers)."\n";
+				}
+			}
+			else
+			{
+				// Fix the issue
+				CMDBSource::Query($sAlterTable);
+				if ($bVerbose)
+				{
+					echo "$sAlterTable\n";
+				}
+
+				foreach($aTriggers as $sSQL)
+				{
+					CMDBSource::Query($sSQL);
+					if ($bVerbose)
+					{
+						echo "$sSQL\n";
+					}
+				}
+			}
+		}
+			
+		return $bFixNeeded;
+	}
 	protected function SendNotification($sSubject, $sBody)
 	protected function SendNotification($sSubject, $sBody)
 	{
 	{
 		$iContact = $this->Get('notify_contact_id');
 		$iContact = $this->Get('notify_contact_id');