/// <summary> /// Add a migration action to delta table. The migration actions will be grouped together to create a change group. /// The following rules are used to group migration actions. /// 1. Action path and fromPath does not collide with any existing paths. /// 2. Action comment is the same. /// 3. Action owner is the same. /// 4. Action time is not more than 10 minutes away from the previous action. /// The return value is the delta table entry the migration action is added to. /// If a new changegroup is generated, oldChangeGroup contains the previous changegroup. /// </summary> /// <param name="defaultGroupName"></param> /// <param name="comment"></param> /// <param name="owner"></param> /// <param name="executionOrder"></param> /// <param name="action"></param> /// <param name="sourceItem"></param> /// <param name="fromPath"></param> /// <param name="path"></param> /// <param name="version"></param> /// <param name="mergeVersionTo"></param> /// <param name="itemTypeRefName"></param> /// <param name="actionDetails"></param> /// <param name="actionTime"></param> /// <param name="oldChangeGroup"></param> /// <returns></returns> public ChangeGroup AddMigrationActionToDeltaTable(string defaultGroupName, string comment, string owner, long executionOrder, Guid action, IMigrationItem sourceItem, string fromPath, string path, string version, string mergeVersionTo, string itemTypeRefName, XmlDocument actionDetails, DateTime actionTime, out ChangeGroup oldChangeGroup) { if (m_currentChangeGroup == null) { oldChangeGroup = null; m_existingActionPaths.Clear(); m_currentChangeGroup = CreateChangeGroupForDeltaTable(defaultGroupName); m_currentChangeGroup.Comment = comment; m_currentChangeGroup.Owner = owner; m_currentChangeGroup.ExecutionOrder = executionOrder; } else if (m_existingActionPaths.Contains(path) || (!string.IsNullOrEmpty(fromPath) && m_existingActionPaths.Contains(fromPath)) || !string.Equals(comment, m_currentChangeGroup.Comment, StringComparison.Ordinal) || !string.Equals(owner, m_currentChangeGroup.Owner, StringComparison.Ordinal) || (m_latestActionTime != DateTime.MinValue && actionTime != DateTime.MinValue && (actionTime - m_latestActionTime > MaxGroupTimeSpan))) { m_currentChangeGroup.Save(); oldChangeGroup = m_currentChangeGroup; m_currentChangeGroup = CreateChangeGroupForDeltaTable(defaultGroupName); m_currentChangeGroup.Comment = comment; m_currentChangeGroup.Owner = owner; m_currentChangeGroup.ExecutionOrder = executionOrder; m_existingActionPaths.Clear(); } else { oldChangeGroup = m_currentChangeGroup; } m_currentChangeGroup.CreateAction(action, sourceItem, fromPath, path, version, mergeVersionTo, itemTypeRefName, actionDetails); m_existingActionPaths.Add(path); if (!string.IsNullOrEmpty(fromPath)) { m_existingActionPaths.Add(fromPath); } m_latestActionTime = actionTime; return(m_currentChangeGroup); }
/// <summary> /// Generate migration instructions /// </summary> internal void GenerateMigrationInstructions(Guid targetSystemId) { try { // Given target system, find change group service for source and for ourselves... ConfigurationService configurationService = m_serviceContainers[targetSystemId].GetService(typeof(ConfigurationService)) as ConfigurationService; // ToDo, not sure, we can probably just pass in ource system id to let target change group service to load it. But source/target may be different, not sqlchangegroupmanager ChangeGroupService sourceChangeGroupService = m_serviceContainers[configurationService.MigrationPeer].GetService(typeof(ChangeGroupService)) as ChangeGroupService; ChangeGroupService targetChangeGroupService = m_serviceContainers[targetSystemId].GetService(typeof(ChangeGroupService)) as ChangeGroupService; // CopySourceDeltaTableToTarget //ChangeGroup deltaTableEntry; if (StopMigrationEngineOnBasicConflict) { // if one of the delta table entry on source side is conflicted, we stop long?firstConflictedChangeGroupId = sourceChangeGroupService.GetFirstConflictedChangeGroup(ChangeStatus.DeltaPending); if (firstConflictedChangeGroupId.HasValue) { return; } // if one of the migration instruction for target side is conflict, we also stop firstConflictedChangeGroupId = targetChangeGroupService.GetFirstConflictedChangeGroup(ChangeStatus.Pending); if (firstConflictedChangeGroupId.HasValue) { return; } } ChangeActionRegistrationService changeActionRegistrationService = m_serviceContainers[targetSystemId].GetService(typeof(ChangeActionRegistrationService)) as ChangeActionRegistrationService; int pageNumber = 0; IEnumerable <ChangeGroup> changeGroups; do { // NOTE: we do not increment pageNumber here, because the processed ChangeGroups are marked "DeltaComplete" and no longer // appear in the delta table changeGroups = sourceChangeGroupService.NextDeltaTablePage(pageNumber, m_pageSize, false); foreach (ChangeGroup deltaTableEntry in changeGroups) { TraceManager.TraceInformation(string.Format( "Generating migration instruction for ChangeGroup {0}", deltaTableEntry.ChangeGroupId)); ChangeGroup migrationInstructionChangeGroup = targetChangeGroupService.CreateChangeGroupForMigrationInstructionTable(deltaTableEntry); // NOTE: // migration instruction change group is created using the target change group manager/service // however, the MigrationItems in it are created by the source-side adapter // by setting the UseOtherSideMigrationItemSerializers flag, we tell this change group to use the source-side change group manager // to find the registered IMigrationItem serializer to persist these MigrationItems migrationInstructionChangeGroup.UseOtherSideMigrationItemSerializers = true; migrationInstructionChangeGroup.ReflectedChangeGroupId = deltaTableEntry.ChangeGroupId; foreach (MigrationAction action in deltaTableEntry.Actions) { try { BeforeCopyDeltaTableEntryToMigrationInstructionTable(action, configurationService.MigrationPeer); } catch (UnmappedWorkItemTypeException unmappedWITEx) { ConflictManager conflictManager = m_serviceContainers[configurationService.MigrationPeer].GetService(typeof(ConflictManager)) as ConflictManager; var conflict = WITUnmappedWITConflictType.CreateConflict(unmappedWITEx.SourceWorkItemType, action); List <MigrationAction> actions; var result = conflictManager.TryResolveNewConflict(conflictManager.SourceId, conflict, out actions); if (!result.Resolved) { continue; } else { if (result.ResolutionType == ConflictResolutionType.SkipConflictedChangeAction) { action.State = ActionState.Skipped; continue; } else { // NOTE: // So far this conflict can only be: // 1. manually resolved (skipped) AFTER // the configuration is updated with the requirement WIT mapping; // 2. skipping the conflicted migration action (i.e. not migrating the source // Work Item type. Debug.Assert( false, string.Format("WITUnmappedWITConflict is auto-resolved. Skipping this assertion will SKIP the original conflicted action '{0}'.", action.ActionId.ToString())); action.State = ActionState.Skipped; continue; } } } if (action.State == ActionState.Skipped || action.ChangeGroup.ContainsBackloggedAction) { continue; } ChangeActionHandler actionHandler; if (changeActionRegistrationService.TryGetChangeActionHandler(action.Action, action.ItemTypeReferenceName, out actionHandler)) { try { actionHandler(action, migrationInstructionChangeGroup); } catch (MigrationUnresolvedConflictException) { // We have already created an unresolved conflict, just return. return; } catch (Exception e) { ConflictManager manager = m_serviceContainers[targetSystemId].GetService(typeof(ConflictManager)) as ConflictManager; ErrorManager.TryHandleException(e, manager); } } else { string analysisProviderName; IAnalysisProvider analysisProvider; if (m_analysisProviders.TryGetValue(targetSystemId, out analysisProvider)) { analysisProviderName = analysisProvider.GetType().ToString(); } else { Debug.Fail("Unable to find IAnalysisProvider with Id: " + targetSystemId); analysisProviderName = "Unknown"; } throw new MigrationException( string.Format(MigrationToolkitResources.Culture, MigrationToolkitResources.UnknownChangeAction, action.Action.ToString(), analysisProviderName)); } } if (!migrationInstructionChangeGroup.ContainsBackloggedAction && migrationInstructionChangeGroup.Actions.Count > 0) { ChangeStatus status = migrationInstructionChangeGroup.Status; migrationInstructionChangeGroup.Status = ChangeStatus.ChangeCreationInProgress; migrationInstructionChangeGroup.Owner = deltaTableEntry.Owner; // owner may be translated too // Save the partial Change group into DB. migrationInstructionChangeGroup.Save(); // Commit the status change together. migrationInstructionChangeGroup.Status = status; deltaTableEntry.Status = ChangeStatus.DeltaComplete; migrationInstructionChangeGroup.Manager.BatchUpdateStatus( new ChangeGroup[] { migrationInstructionChangeGroup, deltaTableEntry }); } else { // If all change actions in the delta table entry are skipped. // Just mark the delta table entry as completed. deltaTableEntry.UpdateStatus(ChangeStatus.DeltaComplete); } if (this.StopRequested) { return; } } }while (changeGroups.Count() == m_pageSize); DetectBasicConflicts(targetChangeGroupService, targetSystemId, configurationService.MigrationPeer); if (this.StopRequested) { return; } ProviderDetectConflicts(targetSystemId, targetChangeGroupService); if (this.StopRequested) { return; } // dispose the target side delta table entries after we've done all conflict analysis targetChangeGroupService.BatchMarkMigrationInstructionsAsPending(); } catch (Exception e) { ConflictManager manager = m_serviceContainers[targetSystemId].GetService(typeof(ConflictManager)) as ConflictManager; ErrorManager.TryHandleException(e, manager); } }