From 3e1f186891e20f987314db154b29a1d3f9fa50f3 Mon Sep 17 00:00:00 2001 From: udeeshagautam <46980425+udeeshagautam@users.noreply.github.com> Date: Fri, 14 Jun 2019 18:19:36 -0700 Subject: [PATCH] Schema compare cancel operation (#826) * First cut of schema compare cancel (private nuget) * Update Dacfx nuget to a published version --- .../Localization/sr.cs | 11 ++++ .../Localization/sr.resx | 3 ++ .../Localization/sr.strings | 3 +- .../Localization/sr.xlf | 5 ++ .../Microsoft.SqlTools.ServiceLayer.csproj | 2 +- .../SchemaCompareCancellationRequest.cs | 30 +++++++++++ .../SchemaCompareGenerateScriptOperation.cs | 3 +- .../SchemaCompare/SchemaCompareOperation.cs | 27 +++++++--- .../SchemaComparePublishChangesOperation.cs | 3 +- .../SchemaCompare/SchemaCompareService.cs | 39 ++++++++++++++ ...ManagedBatchParser.IntegrationTests.csproj | 2 +- ...Tools.ServiceLayer.IntegrationTests.csproj | 2 +- .../SchemaCompareServiceTests.cs | 51 ++++++++++++++++++- 13 files changed, 168 insertions(+), 13 deletions(-) mode change 100755 => 100644 src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs mode change 100755 => 100644 src/Microsoft.SqlTools.ServiceLayer/Localization/sr.resx create mode 100644 src/Microsoft.SqlTools.ServiceLayer/SchemaCompare/Contracts/SchemaCompareCancellationRequest.cs diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs old mode 100755 new mode 100644 index ef2950c6..5b0a1bc5 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.cs @@ -2941,6 +2941,14 @@ namespace Microsoft.SqlTools.ServiceLayer } } + public static string SchemaCompareSessionNotFound + { + get + { + return Keys.GetString(Keys.SchemaCompareSessionNotFound); + } + } + public static string ConnectionServiceListDbErrorNotConnected(string uri) { return Keys.GetString(Keys.ConnectionServiceListDbErrorNotConnected, uri); @@ -4281,6 +4289,9 @@ namespace Microsoft.SqlTools.ServiceLayer public const string OpenScmpConnectionBasedModelParsingError = "OpenScmpConnectionBasedModelParsingError"; + public const string SchemaCompareSessionNotFound = "SchemaCompareSessionNotFound"; + + private Keys() { } diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.resx b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.resx old mode 100755 new mode 100644 index e156dcce..d6dcd3d8 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.resx +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.resx @@ -1723,4 +1723,7 @@ Error encountered while trying to parse connection information for endpoint '{0}' with error message '{1}' + + Could not find the schema compare session to cancel + diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.strings b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.strings index 9b55edcd..3d397753 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.strings +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.strings @@ -797,4 +797,5 @@ ExtractInvalidVersion = Invalid version '{0}' passed. Version must be in the for # Schema Compare PublishChangesTaskName = Apply schema compare changes SchemaCompareExcludeIncludeNodeNotFound = Failed to find the specified change in the model -OpenScmpConnectionBasedModelParsingError = Error encountered while trying to parse connection information for endpoint '{0}' with error message '{1}' \ No newline at end of file +OpenScmpConnectionBasedModelParsingError = Error encountered while trying to parse connection information for endpoint '{0}' with error message '{1}' +SchemaCompareSessionNotFound = Could not find the schema compare session to cancel \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.xlf b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.xlf index 524dc2d8..ca7e18d5 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.xlf +++ b/src/Microsoft.SqlTools.ServiceLayer/Localization/sr.xlf @@ -2001,6 +2001,11 @@ Error encountered while trying to parse connection information for endpoint '{0}' with error message '{1}' + + Could not find the schema compare session to cancel + Could not find the schema compare session to cancel + + \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/Microsoft.SqlTools.ServiceLayer.csproj b/src/Microsoft.SqlTools.ServiceLayer/Microsoft.SqlTools.ServiceLayer.csproj index cb0e88d4..f6041472 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Microsoft.SqlTools.ServiceLayer.csproj +++ b/src/Microsoft.SqlTools.ServiceLayer/Microsoft.SqlTools.ServiceLayer.csproj @@ -23,7 +23,7 @@ - + diff --git a/src/Microsoft.SqlTools.ServiceLayer/SchemaCompare/Contracts/SchemaCompareCancellationRequest.cs b/src/Microsoft.SqlTools.ServiceLayer/SchemaCompare/Contracts/SchemaCompareCancellationRequest.cs new file mode 100644 index 00000000..c6153e43 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/SchemaCompare/Contracts/SchemaCompareCancellationRequest.cs @@ -0,0 +1,30 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using Microsoft.SqlTools.Hosting.Protocol.Contracts; +using Microsoft.SqlTools.ServiceLayer.Utility; + +namespace Microsoft.SqlTools.ServiceLayer.SchemaCompare.Contracts +{ + /// + /// Parameters for a schema compare cancel request + /// + public class SchemaCompareCancelParams + { + /// + /// Operation id of the schema compare operation + /// + public string OperationId { get; set; } + } + + /// + /// Defines the Schema Compare cancel comparison request type + /// + class SchemaCompareCancellationRequest + { + public static readonly RequestType Type = + RequestType.Create("schemaCompare/cancel"); + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/SchemaCompare/SchemaCompareGenerateScriptOperation.cs b/src/Microsoft.SqlTools.ServiceLayer/SchemaCompare/SchemaCompareGenerateScriptOperation.cs index 7b09ed98..37302957 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/SchemaCompare/SchemaCompareGenerateScriptOperation.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/SchemaCompare/SchemaCompareGenerateScriptOperation.cs @@ -57,7 +57,7 @@ namespace Microsoft.SqlTools.ServiceLayer.SchemaCompare try { - this.ScriptGenerationResult = this.ComparisonResult.GenerateScript(this.Parameters.TargetDatabaseName); + this.ScriptGenerationResult = this.ComparisonResult.GenerateScript(this.Parameters.TargetDatabaseName, this.CancellationToken); // tests don't create a SqlTask, so only add the script when the SqlTask isn't null if (this.SqlTask != null) @@ -81,6 +81,7 @@ namespace Microsoft.SqlTools.ServiceLayer.SchemaCompare // The schema compare public api doesn't currently take a cancellation token so the operation can't be cancelled public void Cancel() { + this.cancellation.Cancel(); } /// diff --git a/src/Microsoft.SqlTools.ServiceLayer/SchemaCompare/SchemaCompareOperation.cs b/src/Microsoft.SqlTools.ServiceLayer/SchemaCompare/SchemaCompareOperation.cs index f17d4e7f..7567dc5e 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/SchemaCompare/SchemaCompareOperation.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/SchemaCompare/SchemaCompareOperation.cs @@ -49,7 +49,7 @@ namespace Microsoft.SqlTools.ServiceLayer.SchemaCompare } protected CancellationToken CancellationToken { get { return this.cancellation.Token; } } - + /// /// The error occurred during operation /// @@ -58,6 +58,7 @@ namespace Microsoft.SqlTools.ServiceLayer.SchemaCompare // The schema compare public api doesn't currently take a cancellation token so the operation can't be cancelled public void Cancel() { + this.cancellation.Cancel(); } /// @@ -91,19 +92,31 @@ namespace Microsoft.SqlTools.ServiceLayer.SchemaCompare comparison.Options = SchemaCompareUtils.CreateSchemaCompareOptions(this.Parameters.DeploymentOptions); } - this.ComparisonResult = comparison.Compare(); + // for testing + schemaCompareStarted?.Invoke(this, new EventArgs()); + + this.ComparisonResult = comparison.Compare(this.CancellationToken); // try one more time if it didn't work the first time if (!this.ComparisonResult.IsValid) { - this.ComparisonResult = comparison.Compare(); + this.ComparisonResult = comparison.Compare(this.CancellationToken); + } + + // Since DacFx does not throw on schema comparison cancellation, throwing here explicitly to ensure consistency of behavior + if (!this.ComparisonResult.IsValid && this.CancellationToken.IsCancellationRequested) + { + throw new OperationCanceledException(this.CancellationToken); } this.Differences = new List(); - foreach (SchemaDifference difference in this.ComparisonResult.Differences) + if (this.ComparisonResult.Differences != null) { - DiffEntry diffEntry = SchemaCompareUtils.CreateDiffEntry(difference, null); - this.Differences.Add(diffEntry); + foreach (SchemaDifference difference in this.ComparisonResult.Differences) + { + DiffEntry diffEntry = SchemaCompareUtils.CreateDiffEntry(difference, null); + this.Differences.Add(diffEntry); + } } } catch (Exception e) @@ -113,5 +126,7 @@ namespace Microsoft.SqlTools.ServiceLayer.SchemaCompare throw; } } + + internal event EventHandler schemaCompareStarted; } } diff --git a/src/Microsoft.SqlTools.ServiceLayer/SchemaCompare/SchemaComparePublishChangesOperation.cs b/src/Microsoft.SqlTools.ServiceLayer/SchemaCompare/SchemaComparePublishChangesOperation.cs index 940106b2..3a32bb7c 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/SchemaCompare/SchemaComparePublishChangesOperation.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/SchemaCompare/SchemaComparePublishChangesOperation.cs @@ -54,7 +54,7 @@ namespace Microsoft.SqlTools.ServiceLayer.SchemaCompare try { - this.PublishResult = this.ComparisonResult.PublishChangesToTarget(); + this.PublishResult = this.ComparisonResult.PublishChangesToTarget(this.CancellationToken); } catch (Exception e) { @@ -67,6 +67,7 @@ namespace Microsoft.SqlTools.ServiceLayer.SchemaCompare // The schema compare public api doesn't currently take a cancellation token so the operation can't be cancelled public void Cancel() { + this.cancellation.Cancel(); } } } diff --git a/src/Microsoft.SqlTools.ServiceLayer/SchemaCompare/SchemaCompareService.cs b/src/Microsoft.SqlTools.ServiceLayer/SchemaCompare/SchemaCompareService.cs index 25a77151..20a7ae3c 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/SchemaCompare/SchemaCompareService.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/SchemaCompare/SchemaCompareService.cs @@ -14,6 +14,7 @@ using Microsoft.SqlServer.Dac.Compare; using Microsoft.SqlTools.ServiceLayer.Utility; using Microsoft.SqlTools.Utility; using System.Diagnostics; +using System.Threading; namespace Microsoft.SqlTools.ServiceLayer.SchemaCompare { @@ -27,6 +28,8 @@ namespace Microsoft.SqlTools.ServiceLayer.SchemaCompare private static readonly Lazy instance = new Lazy(() => new SchemaCompareService()); private Lazy> schemaCompareResults = new Lazy>(() => new ConcurrentDictionary()); + private Lazy> currentComparisonCancellationAction = + new Lazy>(() => new ConcurrentDictionary()); // For testability internal Task CurrentSchemaCompareTask; @@ -46,6 +49,7 @@ namespace Microsoft.SqlTools.ServiceLayer.SchemaCompare public void InitializeService(ServiceHost serviceHost) { serviceHost.SetRequestHandler(SchemaCompareRequest.Type, this.HandleSchemaCompareRequest); + serviceHost.SetRequestHandler(SchemaCompareCancellationRequest.Type, this.HandleSchemaCompareCancelRequest); serviceHost.SetRequestHandler(SchemaCompareGenerateScriptRequest.Type, this.HandleSchemaCompareGenerateScriptRequest); serviceHost.SetRequestHandler(SchemaComparePublishChangesRequest.Type, this.HandleSchemaComparePublishChangesRequest); serviceHost.SetRequestHandler(SchemaCompareIncludeExcludeNodeRequest.Type, this.HandleSchemaCompareIncludeExcludeNodeRequest); @@ -78,6 +82,7 @@ namespace Microsoft.SqlTools.ServiceLayer.SchemaCompare try { operation = new SchemaCompareOperation(parameters, sourceConnInfo, targetConnInfo); + currentComparisonCancellationAction.Value[operation.OperationId] = operation.Cancel; operation.Execute(parameters.TaskExecutionMode); // add result to dictionary of results @@ -110,6 +115,40 @@ namespace Microsoft.SqlTools.ServiceLayer.SchemaCompare } } + /// + /// Handles schema compare cancel request + /// + /// + public async Task HandleSchemaCompareCancelRequest(SchemaCompareCancelParams parameters, RequestContext requestContext) + { + try + { + Action cancelAction = null; + if (currentComparisonCancellationAction.Value.TryRemove(parameters.OperationId, out cancelAction)) + { + if(cancelAction != null) + { + cancelAction.Invoke(); + await requestContext.SendResult(new ResultStatus() + { + Success = true, + ErrorMessage = null + }); + } + } + await requestContext.SendResult(new ResultStatus() + { + Success = false, + ErrorMessage = SR.SchemaCompareSessionNotFound + }); + + } + catch (Exception e) + { + await requestContext.SendError(e); + } + } + /// /// Handles request for schema compare generate deploy script /// diff --git a/test/Microsoft.SqlTools.ManagedBatchParser.IntegrationTests/Microsoft.SqlTools.ManagedBatchParser.IntegrationTests.csproj b/test/Microsoft.SqlTools.ManagedBatchParser.IntegrationTests/Microsoft.SqlTools.ManagedBatchParser.IntegrationTests.csproj index 2c473b5b..2245ff18 100644 --- a/test/Microsoft.SqlTools.ManagedBatchParser.IntegrationTests/Microsoft.SqlTools.ManagedBatchParser.IntegrationTests.csproj +++ b/test/Microsoft.SqlTools.ManagedBatchParser.IntegrationTests/Microsoft.SqlTools.ManagedBatchParser.IntegrationTests.csproj @@ -35,7 +35,7 @@ - + diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Microsoft.SqlTools.ServiceLayer.IntegrationTests.csproj b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Microsoft.SqlTools.ServiceLayer.IntegrationTests.csproj index 803dafe2..8894a852 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Microsoft.SqlTools.ServiceLayer.IntegrationTests.csproj +++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Microsoft.SqlTools.ServiceLayer.IntegrationTests.csproj @@ -35,7 +35,7 @@ - + diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/SchemaCompare/SchemaCompareServiceTests.cs b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/SchemaCompare/SchemaCompareServiceTests.cs index d611fb66..bd9f58bd 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/SchemaCompare/SchemaCompareServiceTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/SchemaCompare/SchemaCompareServiceTests.cs @@ -626,6 +626,56 @@ CREATE TABLE [dbo].[table3] } } + /// + /// Verify the schema compare cancel + /// + [Fact] + public async void SchemaCompareCancelCompareOperation() + { + var result = SchemaCompareTestUtils.GetLiveAutoCompleteTestObjects(); + SqlTestDb sourceDb = await SqlTestDb.CreateNewAsync(TestServerType.OnPrem, false, null, SourceScript, "SchemaCompareSource"); + SqlTestDb targetDb = await SqlTestDb.CreateNewAsync(TestServerType.OnPrem, false, null, TargetScript, "SchemaCompareTarget"); + + try + { + SchemaCompareEndpointInfo sourceInfo = new SchemaCompareEndpointInfo(); + SchemaCompareEndpointInfo targetInfo = new SchemaCompareEndpointInfo(); + + sourceInfo.EndpointType = SchemaCompareEndpointType.Database; + sourceInfo.DatabaseName = sourceDb.DatabaseName; + targetInfo.EndpointType = SchemaCompareEndpointType.Database; + targetInfo.DatabaseName = targetDb.DatabaseName; + + var schemaCompareParams = new SchemaCompareParams + { + SourceEndpointInfo = sourceInfo, + TargetEndpointInfo = targetInfo + }; + + SchemaCompareOperation schemaCompareOperation = new SchemaCompareOperation(schemaCompareParams, result.ConnectionInfo, result.ConnectionInfo); + schemaCompareOperation.schemaCompareStarted += (sender, e) => { schemaCompareOperation.Cancel(); }; + + try + { + Task cTask = Task.Factory.StartNew(() => schemaCompareOperation.Execute(TaskExecutionMode.Execute)); + cTask.Wait(); + Assert.False(cTask.IsCompletedSuccessfully, "schema compare task should not complete after cancel"); + } + catch (Exception ex) + { + Assert.NotNull(ex.InnerException); + Assert.True(ex.InnerException is OperationCanceledException, $"Exception is expected to be Operation cancelled but actually is {ex.InnerException}"); + } + + Assert.Null(schemaCompareOperation.ComparisonResult.Differences); + } + finally + { + sourceDb.Cleanup(); + targetDb.Cleanup(); + } + } + private void ValidateSchemaCompareWithExcludeIncludeResults(SchemaCompareOperation schemaCompareOperation) { schemaCompareOperation.Execute(TaskExecutionMode.Execute); @@ -732,7 +782,6 @@ CREATE TABLE [dbo].[table3] Assert.True(initialScript.Length == afterIncludeScript.Length, $"Changes should be same as inital since we included what we excluded, before {initialScript}, now {afterIncludeScript}"); } - private async Task CreateAndOpenScmp(SchemaCompareEndpointType sourceEndpointType, SchemaCompareEndpointType targetEndpointType) { SqlTestDb sourceDb = await SqlTestDb.CreateNewAsync(TestServerType.OnPrem, false, null, SourceScript, "SchemaCompareOpenScmpSource");