diff --git a/src/Microsoft.SqlTools.Credentials/Microsoft.SqlTools.Credentials.csproj b/src/Microsoft.SqlTools.Credentials/Microsoft.SqlTools.Credentials.csproj
index 0e34726e..803a0086 100644
--- a/src/Microsoft.SqlTools.Credentials/Microsoft.SqlTools.Credentials.csproj
+++ b/src/Microsoft.SqlTools.Credentials/Microsoft.SqlTools.Credentials.csproj
@@ -3,7 +3,7 @@
netcoreapp2.2
MicrosoftSqlToolsCredentials
- Exe
+ Exe
false
false
false
@@ -16,21 +16,24 @@
win7-x64;win7-x86;ubuntu.14.04-x64;ubuntu.16.04-x64;centos.7-x64;rhel.7.2-x64;debian.8-x64;fedora.23-x64;opensuse.13.2-x64;osx.10.11-x64;linux-x64
-
+
-
+
+
+
-
+
+
-
+
-
+
diff --git a/src/Microsoft.SqlTools.ServiceLayer/DacFx/Contracts/DacFxRequest.cs b/src/Microsoft.SqlTools.ServiceLayer/DacFx/Contracts/DacFxRequest.cs
new file mode 100644
index 00000000..f5a1d56e
--- /dev/null
+++ b/src/Microsoft.SqlTools.ServiceLayer/DacFx/Contracts/DacFxRequest.cs
@@ -0,0 +1,44 @@
+//
+// 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.TaskServices;
+using Microsoft.SqlTools.ServiceLayer.Utility;
+
+namespace Microsoft.SqlTools.ServiceLayer.DacFx.Contracts
+{
+ ///
+ /// Parameters for a DacFx request.
+ ///
+ public abstract class DacFxParams : IScriptableRequestParams
+ {
+ ///
+ /// Gets or sets package filepath
+ ///
+ public string PackageFilePath { get; set; }
+
+ ///
+ /// Gets or sets name for database
+ ///
+ public string DatabaseName { get; set; }
+
+ ///
+ /// Connection uri
+ ///
+ public string OwnerUri { get; set; }
+
+ ///
+ /// Executation mode for the operation. Default is execution
+ ///
+ public TaskExecutionMode TaskExecutionMode { get; set; }
+ }
+
+ ///
+ /// Parameters returned from a DacFx request.
+ ///
+ public class DacFxResult : ResultStatus
+ {
+ public string OperationId { get; set; }
+ }
+}
diff --git a/src/Microsoft.SqlTools.ServiceLayer/DacFx/Contracts/DeployRequest.cs b/src/Microsoft.SqlTools.ServiceLayer/DacFx/Contracts/DeployRequest.cs
new file mode 100644
index 00000000..8ec1a529
--- /dev/null
+++ b/src/Microsoft.SqlTools.ServiceLayer/DacFx/Contracts/DeployRequest.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.TaskServices;
+using Microsoft.SqlTools.ServiceLayer.Utility;
+
+namespace Microsoft.SqlTools.ServiceLayer.DacFx.Contracts
+{
+ ///
+ /// Parameters for a DacFx deploy request.
+ ///
+ public class DeployParams : DacFxParams
+ {
+ ///
+ /// Gets or sets if upgrading existing database
+ ///
+ public bool UpgradeExisting { get; set; }
+ }
+
+ ///
+ /// Defines the DacFx deploy request type
+ ///
+ class DeployRequest
+ {
+ public static readonly RequestType Type =
+ RequestType.Create("dacfx/deploy");
+ }
+}
diff --git a/src/Microsoft.SqlTools.ServiceLayer/DacFx/Contracts/ExportRequest.cs b/src/Microsoft.SqlTools.ServiceLayer/DacFx/Contracts/ExportRequest.cs
new file mode 100644
index 00000000..c78e0149
--- /dev/null
+++ b/src/Microsoft.SqlTools.ServiceLayer/DacFx/Contracts/ExportRequest.cs
@@ -0,0 +1,26 @@
+//
+// 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.TaskServices;
+using Microsoft.SqlTools.ServiceLayer.Utility;
+
+namespace Microsoft.SqlTools.ServiceLayer.DacFx.Contracts
+{
+ ///
+ /// Parameters for a DacFx export request.
+ ///
+ public class ExportParams : DacFxParams
+ {
+ }
+
+ ///
+ /// Defines the DacFx export request type
+ ///
+ class ExportRequest
+ {
+ public static readonly RequestType Type =
+ RequestType.Create("dacfx/export");
+ }
+}
diff --git a/src/Microsoft.SqlTools.ServiceLayer/DacFx/Contracts/ExtractRequest.cs b/src/Microsoft.SqlTools.ServiceLayer/DacFx/Contracts/ExtractRequest.cs
new file mode 100644
index 00000000..6f904d17
--- /dev/null
+++ b/src/Microsoft.SqlTools.ServiceLayer/DacFx/Contracts/ExtractRequest.cs
@@ -0,0 +1,36 @@
+//
+// 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.TaskServices;
+using Microsoft.SqlTools.ServiceLayer.Utility;
+using System;
+
+namespace Microsoft.SqlTools.ServiceLayer.DacFx.Contracts
+{
+ ///
+ /// Parameters for a DacFx extract request.
+ ///
+ public class ExtractParams : DacFxParams
+ {
+ ///
+ /// Gets or sets the string identifier for the DAC application
+ ///
+ public string ApplicationName { get; set; }
+
+ ///
+ /// Gets or sets the version of the DAC application
+ ///
+ public Version ApplicationVersion { get; set; }
+ }
+
+ ///
+ /// Defines the DacFx extract request type
+ ///
+ class ExtractRequest
+ {
+ public static readonly RequestType Type =
+ RequestType.Create("dacfx/extract");
+ }
+}
diff --git a/src/Microsoft.SqlTools.ServiceLayer/DacFx/Contracts/ImportRequest.cs b/src/Microsoft.SqlTools.ServiceLayer/DacFx/Contracts/ImportRequest.cs
new file mode 100644
index 00000000..69c83f69
--- /dev/null
+++ b/src/Microsoft.SqlTools.ServiceLayer/DacFx/Contracts/ImportRequest.cs
@@ -0,0 +1,27 @@
+//
+// 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.TaskServices;
+using Microsoft.SqlTools.ServiceLayer.Utility;
+
+namespace Microsoft.SqlTools.ServiceLayer.DacFx.Contracts
+{
+ ///
+ /// Parameters for a DacFx import request.
+ ///
+ public class ImportParams : DacFxParams
+ {
+ }
+
+
+ ///
+ /// Defines the DacFx import request type
+ ///
+ class ImportRequest
+ {
+ public static readonly RequestType Type =
+ RequestType.Create("dacfx/import");
+ }
+}
diff --git a/src/Microsoft.SqlTools.ServiceLayer/DacFx/DacFxOperation.cs b/src/Microsoft.SqlTools.ServiceLayer/DacFx/DacFxOperation.cs
new file mode 100644
index 00000000..151760f1
--- /dev/null
+++ b/src/Microsoft.SqlTools.ServiceLayer/DacFx/DacFxOperation.cs
@@ -0,0 +1,93 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+//
+using Microsoft.SqlServer.Dac;
+using Microsoft.SqlTools.ServiceLayer.TaskServices;
+using Microsoft.SqlTools.Utility;
+using System;
+using System.Data.SqlClient;
+using System.Diagnostics;
+using System.Threading;
+
+namespace Microsoft.SqlTools.ServiceLayer.DacFx
+{
+ ///
+ /// Base class for DacFx operations
+ ///
+ abstract class DacFxOperation : ITaskOperation
+ {
+ private CancellationTokenSource cancellation = new CancellationTokenSource();
+ private bool disposed = false;
+
+ ///
+ /// Gets the unique id associated with this instance.
+ ///
+ public string OperationId { get; private set; }
+
+ public SqlTask SqlTask { get; set; }
+
+ protected SqlConnection SqlConnection { get; private set; }
+
+ protected DacServices DacServices { get; private set; }
+
+ protected DacFxOperation(SqlConnection sqlConnection)
+ {
+ Validate.IsNotNull("sqlConnection", sqlConnection);
+ this.SqlConnection = sqlConnection;
+ this.OperationId = Guid.NewGuid().ToString();
+ }
+
+ protected CancellationToken CancellationToken { get { return this.cancellation.Token; } }
+
+ ///
+ /// The error occurred during operation
+ ///
+ public string ErrorMessage { get; }
+
+ ///
+ /// Cancel operation
+ ///
+ public void Cancel()
+ {
+ if (!this.cancellation.IsCancellationRequested)
+ {
+ Logger.Write(TraceEventType.Verbose, string.Format("Cancel invoked for OperationId {0}", this.OperationId));
+ this.cancellation.Cancel();
+ }
+ }
+
+ ///
+ /// Disposes the operation.
+ ///
+ public void Dispose()
+ {
+ if (!disposed)
+ {
+ this.Cancel();
+ disposed = true;
+ }
+ }
+
+ public void Execute(TaskExecutionMode mode)
+ {
+ if (this.CancellationToken.IsCancellationRequested)
+ {
+ throw new OperationCanceledException(this.CancellationToken);
+ }
+
+ try
+ {
+ this.DacServices = new DacServices(this.SqlConnection.ConnectionString);
+ Execute();
+ }
+ catch (Exception e)
+ {
+ Logger.Write(TraceEventType.Error, string.Format("DacFx import operation {0} failed with exception {1}", this.OperationId, e));
+ throw;
+ }
+ }
+
+ public abstract void Execute();
+ }
+}
diff --git a/src/Microsoft.SqlTools.ServiceLayer/DacFx/DacFxService.cs b/src/Microsoft.SqlTools.ServiceLayer/DacFx/DacFxService.cs
new file mode 100644
index 00000000..5e7609b8
--- /dev/null
+++ b/src/Microsoft.SqlTools.ServiceLayer/DacFx/DacFxService.cs
@@ -0,0 +1,215 @@
+//
+// 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;
+using Microsoft.SqlTools.ServiceLayer.Connection;
+using Microsoft.SqlTools.ServiceLayer.DacFx.Contracts;
+using Microsoft.SqlTools.ServiceLayer.Hosting;
+using Microsoft.SqlTools.ServiceLayer.TaskServices;
+using System;
+using System.Collections.Concurrent;
+using System.Data.SqlClient;
+using System.Threading.Tasks;
+
+namespace Microsoft.SqlTools.ServiceLayer.DacFx
+{
+ ///
+ /// Main class for DacFx service
+ ///
+ class DacFxService
+ {
+ private static ConnectionService connectionService = null;
+ private SqlTaskManager sqlTaskManagerInstance = null;
+ private static readonly Lazy instance = new Lazy(() => new DacFxService());
+ private readonly Lazy> operations =
+ new Lazy>(() => new ConcurrentDictionary());
+
+ ///
+ /// Gets the singleton instance object
+ ///
+ public static DacFxService Instance
+ {
+ get { return instance.Value; }
+ }
+
+ ///
+ /// Initializes the service instance
+ ///
+ ///
+ public void InitializeService(ServiceHost serviceHost)
+ {
+ serviceHost.SetRequestHandler(ExportRequest.Type, this.HandleExportRequest);
+ serviceHost.SetRequestHandler(ImportRequest.Type, this.HandleImportRequest);
+ serviceHost.SetRequestHandler(ExtractRequest.Type, this.HandleExtractRequest);
+ serviceHost.SetRequestHandler(DeployRequest.Type, this.HandleDeployRequest);
+ }
+
+ ///
+ /// The collection of active operations
+ ///
+ internal ConcurrentDictionary ActiveOperations => operations.Value;
+
+ ///
+ /// Handles request to export a bacpac
+ ///
+ ///
+ public async Task HandleExportRequest(ExportParams parameters, RequestContext requestContext)
+ {
+ try
+ {
+ ConnectionInfo connInfo;
+ ConnectionServiceInstance.TryFindConnection(
+ parameters.OwnerUri,
+ out connInfo);
+ if (connInfo != null)
+ {
+ SqlConnection sqlConn = ConnectionService.OpenSqlConnection(connInfo, "Export");
+ ExportOperation operation = new ExportOperation(parameters, sqlConn);
+ await ExecuteOperation(operation, parameters, "Export bacpac", requestContext);
+ }
+ }
+ catch (Exception e)
+ {
+ await requestContext.SendError(e);
+ }
+ }
+
+ ///
+ /// Handles request to import a bacpac
+ ///
+ ///
+ public async Task HandleImportRequest(ImportParams parameters, RequestContext requestContext)
+ {
+ try
+ {
+ ConnectionInfo connInfo;
+ ConnectionServiceInstance.TryFindConnection(
+ parameters.OwnerUri,
+ out connInfo);
+ if (connInfo != null)
+ {
+ SqlConnection sqlConn = ConnectionService.OpenSqlConnection(connInfo, "Import");
+ ImportOperation operation = new ImportOperation(parameters, sqlConn);
+ await ExecuteOperation(operation, parameters, "Import bacpac", requestContext);
+ }
+ }
+ catch (Exception e)
+ {
+ await requestContext.SendError(e);
+ }
+ }
+
+ ///
+ /// Handles request to extract a dacpac
+ ///
+ ///
+ public async Task HandleExtractRequest(ExtractParams parameters, RequestContext requestContext)
+ {
+ try
+ {
+ ConnectionInfo connInfo;
+ ConnectionServiceInstance.TryFindConnection(
+ parameters.OwnerUri,
+ out connInfo);
+ if (connInfo != null)
+ {
+ SqlConnection sqlConn = ConnectionService.OpenSqlConnection(connInfo, "Extract");
+ ExtractOperation operation = new ExtractOperation(parameters, sqlConn);
+ await ExecuteOperation(operation, parameters, "Extract dacpac", requestContext);
+ }
+ }
+ catch (Exception e)
+ {
+ await requestContext.SendError(e);
+ }
+ }
+
+ ///
+ /// Handles request to deploy a dacpac
+ ///
+ ///
+ public async Task HandleDeployRequest(DeployParams parameters, RequestContext requestContext)
+ {
+ try
+ {
+ ConnectionInfo connInfo;
+ ConnectionServiceInstance.TryFindConnection(
+ parameters.OwnerUri,
+ out connInfo);
+ if (connInfo != null)
+ {
+ SqlConnection sqlConn = ConnectionService.OpenSqlConnection(connInfo, "Deploy");
+ DeployOperation operation = new DeployOperation(parameters, sqlConn);
+ await ExecuteOperation(operation, parameters, "Deploy dacpac", requestContext);
+ }
+ }
+ catch (Exception e)
+ {
+ await requestContext.SendError(e);
+ }
+ }
+
+ private async Task ExecuteOperation(DacFxOperation operation, DacFxParams parameters, string taskName, RequestContext requestContext)
+ {
+ SqlTask sqlTask = null;
+ TaskMetadata metadata = TaskMetadata.Create(parameters, taskName, operation, ConnectionServiceInstance);
+
+ // put appropriate database name since connection passed was to master
+ metadata.DatabaseName = parameters.DatabaseName;
+
+ sqlTask = SqlTaskManagerInstance.CreateAndRun(metadata);
+
+ await requestContext.SendResult(new DacFxResult()
+ {
+ OperationId = operation.OperationId,
+ Success = true,
+ ErrorMessage = ""
+ });
+ }
+
+ private SqlTaskManager SqlTaskManagerInstance
+ {
+ get
+ {
+ if (sqlTaskManagerInstance == null)
+ {
+ sqlTaskManagerInstance = SqlTaskManager.Instance;
+ }
+ return sqlTaskManagerInstance;
+ }
+ set
+ {
+ sqlTaskManagerInstance = value;
+ }
+ }
+
+ ///
+ /// Internal for testing purposes only
+ ///
+ internal static ConnectionService ConnectionServiceInstance
+ {
+ get
+ {
+ if (connectionService == null)
+ {
+ connectionService = ConnectionService.Instance;
+ }
+ return connectionService;
+ }
+ set
+ {
+ connectionService = value;
+ }
+ }
+
+ ///
+ /// For testing purpose only
+ ///
+ ///
+ internal void PerformOperation(DacFxOperation operation)
+ {
+ operation.Execute(TaskExecutionMode.Execute);
+ }
+ }
+}
diff --git a/src/Microsoft.SqlTools.ServiceLayer/DacFx/DeployOperation.cs b/src/Microsoft.SqlTools.ServiceLayer/DacFx/DeployOperation.cs
new file mode 100644
index 00000000..b636adcc
--- /dev/null
+++ b/src/Microsoft.SqlTools.ServiceLayer/DacFx/DeployOperation.cs
@@ -0,0 +1,34 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+//
+using Microsoft.SqlServer.Dac;
+using Microsoft.SqlTools.ServiceLayer.DacFx.Contracts;
+using Microsoft.SqlTools.ServiceLayer.TaskServices;
+using Microsoft.SqlTools.Utility;
+using System;
+using System.Data.SqlClient;
+using System.Diagnostics;
+
+namespace Microsoft.SqlTools.ServiceLayer.DacFx
+{
+ ///
+ /// Class to represent an in-progress deploy operation
+ ///
+ class DeployOperation : DacFxOperation
+ {
+ public DeployParams Parameters { get; }
+
+ public DeployOperation(DeployParams parameters, SqlConnection sqlConnection) : base(sqlConnection)
+ {
+ Validate.IsNotNull("parameters", parameters);
+ this.Parameters = parameters;
+ }
+
+ public override void Execute()
+ {
+ DacPackage dacpac = DacPackage.Load(this.Parameters.PackageFilePath);
+ this.DacServices.Deploy(dacpac, this.Parameters.DatabaseName, this.Parameters.UpgradeExisting, null, this.CancellationToken);
+ }
+ }
+}
diff --git a/src/Microsoft.SqlTools.ServiceLayer/DacFx/ExportOperation.cs b/src/Microsoft.SqlTools.ServiceLayer/DacFx/ExportOperation.cs
new file mode 100644
index 00000000..6cda9068
--- /dev/null
+++ b/src/Microsoft.SqlTools.ServiceLayer/DacFx/ExportOperation.cs
@@ -0,0 +1,33 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+//
+using Microsoft.SqlServer.Dac;
+using Microsoft.SqlTools.ServiceLayer.DacFx.Contracts;
+using Microsoft.SqlTools.ServiceLayer.TaskServices;
+using Microsoft.SqlTools.Utility;
+using System;
+using System.Data.SqlClient;
+using System.Diagnostics;
+
+namespace Microsoft.SqlTools.ServiceLayer.DacFx
+{
+ ///
+ /// Class to represent an in-progress export operation
+ ///
+ class ExportOperation : DacFxOperation
+ {
+ public ExportParams Parameters { get; }
+
+ public ExportOperation(ExportParams parameters, SqlConnection sqlConnection): base(sqlConnection)
+ {
+ Validate.IsNotNull("parameters", parameters);
+ this.Parameters = parameters;
+ }
+
+ public override void Execute()
+ {
+ this.DacServices.ExportBacpac(this.Parameters.PackageFilePath, this.Parameters.DatabaseName, null, this.CancellationToken);
+ }
+ }
+}
diff --git a/src/Microsoft.SqlTools.ServiceLayer/DacFx/ExtractOperation.cs b/src/Microsoft.SqlTools.ServiceLayer/DacFx/ExtractOperation.cs
new file mode 100644
index 00000000..09b6b130
--- /dev/null
+++ b/src/Microsoft.SqlTools.ServiceLayer/DacFx/ExtractOperation.cs
@@ -0,0 +1,33 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+//
+using Microsoft.SqlServer.Dac;
+using Microsoft.SqlTools.ServiceLayer.DacFx.Contracts;
+using Microsoft.SqlTools.ServiceLayer.TaskServices;
+using Microsoft.SqlTools.Utility;
+using System;
+using System.Data.SqlClient;
+using System.Diagnostics;
+
+namespace Microsoft.SqlTools.ServiceLayer.DacFx
+{
+ ///
+ /// Class to represent an in-progress extract operation
+ ///
+ class ExtractOperation : DacFxOperation
+ {
+ public ExtractParams Parameters { get; }
+
+ public ExtractOperation(ExtractParams parameters, SqlConnection sqlConnection): base(sqlConnection)
+ {
+ Validate.IsNotNull("parameters", parameters);
+ this.Parameters = parameters;
+ }
+
+ public override void Execute()
+ {
+ this.DacServices.Extract(this.Parameters.PackageFilePath, this.Parameters.DatabaseName, this.Parameters.ApplicationName, this.Parameters.ApplicationVersion, null, null, null, this.CancellationToken);
+ }
+ }
+}
diff --git a/src/Microsoft.SqlTools.ServiceLayer/DacFx/ImportOperation.cs b/src/Microsoft.SqlTools.ServiceLayer/DacFx/ImportOperation.cs
new file mode 100644
index 00000000..fbc51347
--- /dev/null
+++ b/src/Microsoft.SqlTools.ServiceLayer/DacFx/ImportOperation.cs
@@ -0,0 +1,34 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+//
+using Microsoft.SqlServer.Dac;
+using Microsoft.SqlTools.ServiceLayer.DacFx.Contracts;
+using Microsoft.SqlTools.ServiceLayer.TaskServices;
+using Microsoft.SqlTools.Utility;
+using System;
+using System.Data.SqlClient;
+using System.Diagnostics;
+
+namespace Microsoft.SqlTools.ServiceLayer.DacFx
+{
+ ///
+ /// Class to represent an in-progress import operation
+ ///
+ class ImportOperation : DacFxOperation
+ {
+ public ImportParams Parameters { get; }
+
+ public ImportOperation(ImportParams parameters, SqlConnection sqlConnection): base(sqlConnection)
+ {
+ Validate.IsNotNull("parameters", parameters);
+ this.Parameters = parameters;
+ }
+
+ public override void Execute()
+ {
+ BacPackage bacpac = BacPackage.Load(this.Parameters.PackageFilePath);
+ this.DacServices.ImportBacpac(bacpac, this.Parameters.DatabaseName, this.CancellationToken);
+ }
+ }
+}
diff --git a/src/Microsoft.SqlTools.ServiceLayer/HostLoader.cs b/src/Microsoft.SqlTools.ServiceLayer/HostLoader.cs
index 790574ea..d9a12994 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/HostLoader.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/HostLoader.cs
@@ -11,6 +11,7 @@ using Microsoft.SqlTools.Hosting.Protocol;
using Microsoft.SqlTools.ServiceLayer.Admin;
using Microsoft.SqlTools.ServiceLayer.Agent;
using Microsoft.SqlTools.ServiceLayer.Connection;
+using Microsoft.SqlTools.ServiceLayer.DacFx;
using Microsoft.SqlTools.ServiceLayer.DisasterRecovery;
using Microsoft.SqlTools.ServiceLayer.EditData;
using Microsoft.SqlTools.ServiceLayer.FileBrowser;
@@ -110,6 +111,9 @@ namespace Microsoft.SqlTools.ServiceLayer
SecurityService.Instance.InitializeService(serviceHost);
serviceProvider.RegisterSingleService(SecurityService.Instance);
+ DacFxService.Instance.InitializeService(serviceHost);
+ serviceProvider.RegisterSingleService(DacFxService.Instance);
+
InitializeHostedServices(serviceProvider, serviceHost);
serviceHost.ServiceProvider = serviceProvider;
diff --git a/src/Microsoft.SqlTools.ServiceLayer/Microsoft.SqlTools.ServiceLayer.csproj b/src/Microsoft.SqlTools.ServiceLayer/Microsoft.SqlTools.ServiceLayer.csproj
index 4d8484cd..7ea2aae8 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/Microsoft.SqlTools.ServiceLayer.csproj
+++ b/src/Microsoft.SqlTools.ServiceLayer/Microsoft.SqlTools.ServiceLayer.csproj
@@ -16,13 +16,14 @@
win7-x64;win7-x86;ubuntu.14.04-x64;ubuntu.16.04-x64;centos.7-x64;rhel.7.2-x64;debian.8-x64;fedora.23-x64;opensuse.13.2-x64;osx.10.11-x64;linux-x64
-
- true
-
+
+ true
+
+
diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/DacFx/DacFxserviceTests.cs b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/DacFx/DacFxserviceTests.cs
new file mode 100644
index 00000000..612bda07
--- /dev/null
+++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/DacFx/DacFxserviceTests.cs
@@ -0,0 +1,276 @@
+//
+// 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;
+using Microsoft.SqlTools.ServiceLayer.Connection;
+using Microsoft.SqlTools.ServiceLayer.DacFx;
+using Microsoft.SqlTools.ServiceLayer.DacFx.Contracts;
+using Microsoft.SqlTools.ServiceLayer.IntegrationTests.Utility;
+using Microsoft.SqlTools.ServiceLayer.Test.Common;
+using Moq;
+using System;
+using System.Data.SqlClient;
+using System.IO;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.DacFx
+{
+ public class DacFxServiceTests
+ {
+
+ private LiveConnectionHelper.TestConnectionResult GetLiveAutoCompleteTestObjects()
+ {
+ var result = LiveConnectionHelper.InitLiveConnectionInfo();
+ return result;
+ }
+
+ private async Task>> SendAndValidateExportRequest()
+ {
+ var result = GetLiveAutoCompleteTestObjects();
+ var requestContext = new Mock>();
+ requestContext.Setup(x => x.SendResult(It.IsAny())).Returns(Task.FromResult(new object()));
+
+ SqlTestDb testdb = await SqlTestDb.CreateNewAsync(TestServerType.OnPrem, false, null, null, "DacFxExportTest");
+ string folderPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "DacFxTest");
+ Directory.CreateDirectory(folderPath);
+
+ var exportParams = new ExportParams
+ {
+ DatabaseName = testdb.DatabaseName,
+ PackageFilePath = Path.Combine(folderPath, string.Format("{0}.bacpac", testdb.DatabaseName))
+ };
+
+ SqlConnection sqlConn = ConnectionService.OpenSqlConnection(result.ConnectionInfo, "Export");
+ DacFxService service = new DacFxService();
+ ExportOperation operation = new ExportOperation(exportParams, sqlConn);
+ service.PerformOperation(operation);
+
+ // cleanup
+ VerifyAndCleanup(exportParams.PackageFilePath);
+ testdb.Cleanup();
+
+ return requestContext;
+ }
+
+ private async Task>> SendAndValidateImportRequest()
+ {
+ // first export a bacpac
+ var result = GetLiveAutoCompleteTestObjects();
+ var exportRequestContext = new Mock>();
+ exportRequestContext.Setup(x => x.SendResult(It.IsAny())).Returns(Task.FromResult(new object()));
+
+ SqlTestDb sourceDb = await SqlTestDb.CreateNewAsync(TestServerType.OnPrem, false, null, null, "DacFxImportTest");
+ string folderPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "DacFxTest");
+ Directory.CreateDirectory(folderPath);
+
+ var exportParams = new ExportParams
+ {
+ DatabaseName = sourceDb.DatabaseName,
+ PackageFilePath = Path.Combine(folderPath, string.Format("{0}.bacpac", sourceDb.DatabaseName))
+ };
+
+ SqlConnection sqlConn = ConnectionService.OpenSqlConnection(result.ConnectionInfo, "Import");
+ DacFxService service = new DacFxService();
+ ExportOperation exportOperation = new ExportOperation(exportParams, sqlConn);
+ service.PerformOperation(exportOperation);
+
+ // import the created bacpac
+ var importRequestContext = new Mock>();
+ importRequestContext.Setup(x => x.SendResult(It.IsAny())).Returns(Task.FromResult(new object()));
+
+ var importParams = new ImportParams
+ {
+ PackageFilePath = exportParams.PackageFilePath,
+ DatabaseName = string.Concat(sourceDb.DatabaseName, "-imported")
+ };
+
+ ImportOperation importOperation = new ImportOperation(importParams, sqlConn);
+ service.PerformOperation(importOperation);
+ SqlTestDb targetDb = SqlTestDb.CreateFromExisting(importParams.DatabaseName);
+
+ // cleanup
+ VerifyAndCleanup(exportParams.PackageFilePath);
+ sourceDb.Cleanup();
+ targetDb.Cleanup();
+
+ return importRequestContext;
+ }
+
+ private async Task>> SendAndValidateExtractRequest()
+ {
+ var result = GetLiveAutoCompleteTestObjects();
+ var requestContext = new Mock>();
+ requestContext.Setup(x => x.SendResult(It.IsAny())).Returns(Task.FromResult(new object()));
+
+ SqlTestDb testdb = await SqlTestDb.CreateNewAsync(TestServerType.OnPrem, false, null, null, "DacFxExtractTest");
+ string folderPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "DacFxTest");
+ Directory.CreateDirectory(folderPath);
+
+ var extractParams = new ExtractParams
+ {
+ DatabaseName = testdb.DatabaseName,
+ PackageFilePath = Path.Combine(folderPath, string.Format("{0}.dacpac", testdb.DatabaseName)),
+ ApplicationName = "test",
+ ApplicationVersion = new Version(1, 0)
+ };
+
+ SqlConnection sqlConn = ConnectionService.OpenSqlConnection(result.ConnectionInfo, "Extract");
+ DacFxService service = new DacFxService();
+ ExtractOperation operation = new ExtractOperation(extractParams, sqlConn);
+ service.PerformOperation(operation);
+
+ // cleanup
+ VerifyAndCleanup(extractParams.PackageFilePath);
+ testdb.Cleanup();
+
+ return requestContext;
+ }
+
+ private async Task>> SendAndValidateDeployRequest()
+ {
+ // first extract a db to have a dacpac to import later
+ var result = GetLiveAutoCompleteTestObjects();
+ var extractRequestContext = new Mock>();
+ extractRequestContext.Setup(x => x.SendResult(It.IsAny())).Returns(Task.FromResult(new object()));
+
+ SqlTestDb sourceDb = await SqlTestDb.CreateNewAsync(TestServerType.OnPrem, false, null, null, "DacFxDeployTest");
+ string folderPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "DacFxTest");
+ Directory.CreateDirectory(folderPath);
+
+ var extractParams = new ExtractParams
+ {
+ DatabaseName = sourceDb.DatabaseName,
+ PackageFilePath = Path.Combine(folderPath, string.Format("{0}.dacpac", sourceDb.DatabaseName)),
+ ApplicationName = "test",
+ ApplicationVersion = new Version(1, 0)
+ };
+
+ SqlConnection sqlConn = ConnectionService.OpenSqlConnection(result.ConnectionInfo, "Deploy");
+ DacFxService service = new DacFxService();
+ ExtractOperation extractOperation = new ExtractOperation(extractParams, sqlConn);
+ service.PerformOperation(extractOperation);
+
+ // deploy the created dacpac
+ var deployRequestContext = new Mock>();
+ deployRequestContext.Setup(x => x.SendResult(It.IsAny())).Returns(Task.FromResult(new object()));
+
+
+ var deployParams = new DeployParams
+ {
+ PackageFilePath = extractParams.PackageFilePath,
+ DatabaseName = string.Concat(sourceDb.DatabaseName, "-deployed"),
+ UpgradeExisting = false
+ };
+
+ DeployOperation deployOperation = new DeployOperation(deployParams, sqlConn);
+ service.PerformOperation(deployOperation); SqlTestDb targetDb = SqlTestDb.CreateFromExisting(deployParams.DatabaseName);
+
+ // cleanup
+ VerifyAndCleanup(extractParams.PackageFilePath);
+ sourceDb.Cleanup();
+ targetDb.Cleanup();
+
+ return deployRequestContext;
+ }
+
+ private async Task>> ValidateExportCancellation()
+ {
+ var result = GetLiveAutoCompleteTestObjects();
+ var requestContext = new Mock>();
+ requestContext.Setup(x => x.SendResult(It.IsAny())).Returns(Task.FromResult(new object()));
+
+ SqlTestDb testdb = await SqlTestDb.CreateNewAsync(TestServerType.OnPrem, false, null, null, "DacFxExportTest");
+ string folderPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "DacFxTest");
+ Directory.CreateDirectory(folderPath);
+
+ var exportParams = new ExportParams
+ {
+ DatabaseName = testdb.DatabaseName,
+ PackageFilePath = Path.Combine(folderPath, string.Format("{0}.bacpac", testdb.DatabaseName))
+ };
+
+ SqlConnection sqlConn = ConnectionService.OpenSqlConnection(result.ConnectionInfo, "Export");
+ DacFxService service = new DacFxService();
+ ExportOperation operation = new ExportOperation(exportParams, sqlConn);
+
+ // set cancellation token to cancel
+ operation.Cancel();
+ OperationCanceledException expectedException = null;
+
+ try
+ {
+ service.PerformOperation(operation);
+ }
+ catch(OperationCanceledException canceledException)
+ {
+ expectedException = canceledException;
+ }
+
+ Assert.NotNull(expectedException);
+
+ // cleanup
+ testdb.Cleanup();
+
+ return requestContext;
+ }
+
+ ///
+ /// Verify the export bacpac request
+ ///
+ [Fact]
+ public async void ExportBacpac()
+ {
+ Assert.NotNull(await SendAndValidateExportRequest());
+ }
+
+ ///
+ /// Verify the export request being cancelled
+ ///
+ [Fact]
+ public async void ExportBacpacCancellationTest()
+ {
+ Assert.NotNull(await ValidateExportCancellation());
+ }
+
+ ///
+ /// Verify the import bacpac request
+ ///
+ [Fact]
+ public async void ImportBacpac()
+ {
+ Assert.NotNull(await SendAndValidateImportRequest());
+ }
+
+ ///
+ /// Verify the extract dacpac request
+ ///
+ [Fact]
+ public async void ExtractDacpac()
+ {
+ Assert.NotNull(await SendAndValidateExtractRequest());
+ }
+
+ ///
+ /// Verify the deploy dacpac request
+ ///
+ [Fact]
+ public async void DeployDacpac()
+ {
+ Assert.NotNull(await SendAndValidateDeployRequest());
+ }
+
+ private void VerifyAndCleanup(string filePath)
+ {
+ // Verify it was created
+ Assert.True(File.Exists(filePath));
+
+ // Remove the file
+ if (File.Exists(filePath))
+ {
+ File.Delete(filePath);
+ }
+ }
+ }
+}
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 b1b1f5de..23877549 100644
--- a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Microsoft.SqlTools.ServiceLayer.IntegrationTests.csproj
+++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Microsoft.SqlTools.ServiceLayer.IntegrationTests.csproj
@@ -35,6 +35,7 @@
+
diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/SqlTestDb.cs b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/SqlTestDb.cs
index bdc0796f..40d0cc53 100644
--- a/test/Microsoft.SqlTools.ServiceLayer.Test.Common/SqlTestDb.cs
+++ b/test/Microsoft.SqlTools.ServiceLayer.Test.Common/SqlTestDb.cs
@@ -110,6 +110,27 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Common
return CreateNew(serverType, false, null, null);
}
+ ///
+ /// Represents a test Database that was created in a test
+ ///
+ public static SqlTestDb CreateFromExisting(
+ string dbName,
+ TestServerType serverType = TestServerType.OnPrem,
+ bool doNotCleanupDb = false)
+ {
+ SqlTestDb testDb = new SqlTestDb();
+
+ if (string.IsNullOrEmpty(dbName))
+ {
+ throw new ArgumentOutOfRangeException("dbName");
+ }
+
+ testDb.DatabaseName = dbName;
+ testDb.ServerType = serverType;
+
+ return testDb;
+ }
+
///
/// Returns a mangled name that unique based on Prefix + Machine + Process
///