diff --git a/Common.props b/Common.props index 1aa4820ed8..4e75c1bf4d 100644 --- a/Common.props +++ b/Common.props @@ -1,7 +1,7 @@ $(MSBuildAllProjects);$(MSBuildThisFileFullPath) - 150.18040.0-preview + 150.18085.0-preview true true diff --git a/nuget.config b/nuget.config index 253012993d..f7e745a46d 100644 --- a/nuget.config +++ b/nuget.config @@ -1,9 +1,9 @@ - - + + - + diff --git a/src/Microsoft.SqlTools.ManagedBatchParser/Microsoft.SqlTools.ManagedBatchParser.csproj b/src/Microsoft.SqlTools.ManagedBatchParser/Microsoft.SqlTools.ManagedBatchParser.csproj index 7c63cc89c4..b3239405d3 100644 --- a/src/Microsoft.SqlTools.ManagedBatchParser/Microsoft.SqlTools.ManagedBatchParser.csproj +++ b/src/Microsoft.SqlTools.ManagedBatchParser/Microsoft.SqlTools.ManagedBatchParser.csproj @@ -1,4 +1,5 @@  + netstandard2.0 false @@ -8,7 +9,7 @@ - + diff --git a/src/Microsoft.SqlTools.ServiceLayer/Cms/CmsService.cs b/src/Microsoft.SqlTools.ServiceLayer/Cms/CmsService.cs new file mode 100644 index 0000000000..050ac36f9e --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Cms/CmsService.cs @@ -0,0 +1,410 @@ +// +// 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.Management.Common; +using Microsoft.SqlServer.Management.RegisteredServers; +using Microsoft.SqlServer.Management.Sdk.Sfc; +using Microsoft.SqlTools.Hosting.Protocol; +using Microsoft.SqlTools.ServiceLayer.Cms.Contracts; +using Microsoft.SqlTools.ServiceLayer.Connection; +using Microsoft.SqlTools.ServiceLayer.Connection.Contracts; +using Microsoft.SqlTools.ServiceLayer.Hosting; +using Microsoft.SqlTools.Utility; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading.Tasks; + +namespace Microsoft.SqlTools.ServiceLayer.Cms +{ + /// + /// Main class for CmsService + /// + public class CmsService + { + private static ConnectionService connectionService = null; + private static readonly Lazy instance = new Lazy(() => new CmsService()); + + /// + /// Gets the singleton instance object + /// + public static CmsService Instance + { + get { return instance.Value; } + } + + #region Public methods + + /// + /// Initializes the service instance + /// + /// + public void InitializeService(ServiceHost serviceHost) + { + serviceHost.SetRequestHandler(CreateCentralManagementServerRequest.Type, this.HandleCreateCentralManagementServerRequest); + serviceHost.SetRequestHandler(ListRegisteredServersRequest.Type, this.HandleListRegisteredServersRequest); + serviceHost.SetRequestHandler(AddRegisteredServerRequest.Type, this.HandleAddRegisteredServerRequest); + serviceHost.SetRequestHandler(RemoveRegisteredServerRequest.Type, this.HandleRemoveRegisteredServerRequest); + serviceHost.SetRequestHandler(AddServerGroupRequest.Type, this.HandleAddServerGroupRequest); + serviceHost.SetRequestHandler(RemoveServerGroupRequest.Type, this.HandleRemoveServerGroupRequest); + } + + public async Task HandleCreateCentralManagementServerRequest(CreateCentralManagementServerParams createCmsParams, RequestContext requestContext) + { + Logger.Write(TraceEventType.Verbose, "HandleCreateCentralManagementServerRequest"); + try + { + CmsTask = Task.Run(async () => + { + try + { + //Validate params and connect + ServerConnection conn = await ValidateAndCreateConnection(createCmsParams.ConnectParams); + + // Get Current Reg Servers on CMS + RegisteredServersStore store = new RegisteredServersStore(conn); + ServerGroup parentGroup = store.DatabaseEngineServerGroup; + ListRegisteredServersResult result = GetChildrenfromParentGroup(parentGroup); + if (result != null) + { + await requestContext.SendResult(result); + return; + } + } + catch (Exception ex) + { + // Exception related to connection/creation will only be caught here. Note that the outer catch will not catch them + await requestContext.SendError(ex); + } + }); + } + catch (Exception e) + { + // Exception related to run task will be captured here + await requestContext.SendError(e); + } + } + + public async Task HandleAddRegisteredServerRequest(AddRegisteredServerParams cmsCreateParams, RequestContext requestContext) + { + Logger.Write(TraceEventType.Verbose, "HandleAddRegisteredServerRequest"); + try + { + CmsTask = Task.Run(async () => + { + try + { + ServerConnection serverConn = ValidateAndCreateConnection(cmsCreateParams.ParentOwnerUri); + if (serverConn != null) + { + // Get Current Reg Servers + RegisteredServersStore store = new RegisteredServersStore(serverConn); + ServerGroup parentGroup = NavigateToServerGroup(store, cmsCreateParams.RelativePath); + RegisteredServerCollection servers = parentGroup.RegisteredServers; + + // Add the new server (intentionally not cheching existence to reuse the exception message) + RegisteredServer registeredServer = new RegisteredServer(parentGroup, cmsCreateParams.RegisteredServerName); + if (cmsCreateParams.RegisteredServerConnectionDetails != null) + { + registeredServer.ServerName = cmsCreateParams.RegisteredServerConnectionDetails.ServerName; + registeredServer.Description = cmsCreateParams.RegisteredServerDescription; + registeredServer.ConnectionString = ConnectionService.CreateConnectionStringBuilder(cmsCreateParams.RegisteredServerConnectionDetails).ToString(); + } + registeredServer.Description = cmsCreateParams.RegisteredServerDescription; + registeredServer.Create(); + await requestContext.SendResult(true); + } + else + { + await requestContext.SendResult(false); + } + } + catch (Exception e) + { + await requestContext.SendError(e); + } + }); + } + catch (Exception e) + { + await requestContext.SendError(e); + } + } + + public async Task HandleListRegisteredServersRequest(ListRegisteredServerParams listServerParams, RequestContext requestContext) + { + Logger.Write(TraceEventType.Verbose, "HandleListRegisteredServersRequest"); + try + { + CmsTask = Task.Run(async () => + { + try + { + //Validate and create connection + ServerConnection serverConn = ValidateAndCreateConnection(listServerParams.ParentOwnerUri); + + if (serverConn != null) + { + // Get registered Servers + RegisteredServersStore store = new RegisteredServersStore(serverConn); + ServerGroup parentGroup = NavigateToServerGroup(store, listServerParams.RelativePath); + + ListRegisteredServersResult result = GetChildrenfromParentGroup(parentGroup); + await requestContext.SendResult(result); + } + else + { + await requestContext.SendResult(null); + } + } + catch (Exception e) + { + await requestContext.SendError(e); + } + }); + } + catch (Exception e) + { + await requestContext.SendError(e); + } + } + + public async Task HandleRemoveRegisteredServerRequest(RemoveRegisteredServerParams removeServerParams, RequestContext requestContext) + { + Logger.Write(TraceEventType.Verbose, "HandleRemoveServerRequest"); + try + { + CmsTask = Task.Run(async () => + { + try + { + // Validate and Connect + ServerConnection serverConn = ValidateAndCreateConnection(removeServerParams.ParentOwnerUri); + if (serverConn != null) + { + // Get list of registered Servers + RegisteredServersStore store = new RegisteredServersStore(serverConn); + ServerGroup parentGroup = NavigateToServerGroup(store, removeServerParams.RelativePath, false); + if (parentGroup != null) + { + RegisteredServer regServ = parentGroup.RegisteredServers.OfType().FirstOrDefault(r => r.Name == removeServerParams.RegisteredServerName); // since duplicates are not allowed + regServ?.Drop(); + await requestContext.SendResult(true); + } + } + else + { + await requestContext.SendResult(false); + } + } + catch (Exception e) + { + await requestContext.SendError(e); + } + }); + } + catch (Exception e) + { + await requestContext.SendError(e); + } + } + + public async Task HandleAddServerGroupRequest(AddServerGroupParams addServerGroupParams, RequestContext requestContext) + { + Logger.Write(TraceEventType.Verbose, "HandleAddServerGroupRequest"); + try + { + CmsTask = Task.Run(async () => + { + try + { + ServerConnection serverConn = ValidateAndCreateConnection(addServerGroupParams.ParentOwnerUri); + RegisteredServersStore store = new RegisteredServersStore(serverConn); + ServerGroup parentGroup = NavigateToServerGroup(store, addServerGroupParams.RelativePath); + + // Add the new group (intentionally not cheching existence to reuse the exception message) + ServerGroup serverGroup = new ServerGroup(parentGroup, addServerGroupParams.GroupName) + { + Description = addServerGroupParams.GroupDescription + }; + serverGroup.Create(); + await requestContext.SendResult(true); + } + catch (Exception e) + { + await requestContext.SendError(e); + } + }); + } + catch (Exception e) + { + await requestContext.SendError(e); + } + } + + public async Task HandleRemoveServerGroupRequest(RemoveServerGroupParams removeServerGroupParams, RequestContext requestContext) + { + Logger.Write(TraceEventType.Verbose, "HandleRemoveServerGroupRequest"); + try + { + CmsTask = Task.Run(async () => + { + try + { + ServerConnection serverConn = ValidateAndCreateConnection(removeServerGroupParams.ParentOwnerUri); + if (serverConn != null) + { + RegisteredServersStore store = new RegisteredServersStore(serverConn); + + ServerGroup parentGroup = NavigateToServerGroup(store, removeServerGroupParams.RelativePath, false); + ServerGroup serverGrouptoRemove = parentGroup.ServerGroups.OfType().FirstOrDefault(r => r.Name == removeServerGroupParams.GroupName); // since duplicates are not allowed + serverGrouptoRemove?.Drop(); + await requestContext.SendResult(true); + } + else + { + await requestContext.SendResult(false); + } + } + catch (Exception e) + { + await requestContext.SendError(e); + } + }); + } + catch (Exception e) + { + await requestContext.SendError(e); + } + } + + #endregion + + #region Private methods + + private ServerGroup NavigateToServerGroup(RegisteredServersStore store, string relativePath, bool alreadyParent = true) + { + if (string.IsNullOrEmpty(relativePath)) + { + return store.DatabaseEngineServerGroup; + } + + // Get key chain from URN + Urn urn = new Urn(relativePath); + SfcKeyChain keyChain = alreadyParent ? new SfcKeyChain(urn, store as ISfcDomain) : new SfcKeyChain(urn, store as ISfcDomain).Parent; + + ServerGroup parentGroup = GetNodeFromKeyChain(keyChain, store.DatabaseEngineServerGroup); + return parentGroup; + } + + private ServerGroup GetNodeFromKeyChain(SfcKeyChain keyChain, ServerGroup rootServerGroup) + { + if (keyChain == rootServerGroup.KeyChain) + { + return rootServerGroup; + } + if (keyChain != rootServerGroup.KeyChain) + { + var parent = GetNodeFromKeyChain(keyChain.Parent, rootServerGroup); + if (parent != null && parent is ServerGroup) + { + var server = (parent as ServerGroup).ServerGroups.FirstOrDefault(x => x.KeyChain == keyChain); + return server; + } + } + return null; + } + + private async Task ValidateAndCreateConnection(ConnectParams connectionParams) + { + // Validate Parameters and Create Connection + ConnectionCompleteParams connectionCompleteParams = await ConnectionServiceInstance.Connect(connectionParams); + if (!string.IsNullOrEmpty(connectionCompleteParams.Messages)) + { + throw new Exception(connectionCompleteParams.Messages); + } + + // Get Connection + ConnectionInfo connectionInfo = ConnectionServiceInstance.OwnerToConnectionMap[connectionParams.OwnerUri]; + ServerConnection serverConn = ConnectionService.OpenServerConnection(connectionInfo); + + return serverConn; + } + + private ServerConnection ValidateAndCreateConnection(string ownerUri) + { + ServerConnection serverConn = null; + if (ownerUri != null) + { + ConnectionInfo connInfo = null; + if (ConnectionServiceInstance.TryFindConnection(ownerUri, out connInfo)) + { + serverConn = ConnectionService.OpenServerConnection(connInfo); + } + } + return serverConn; + } + + private ListRegisteredServersResult GetChildrenfromParentGroup(ServerGroup parentGroup) + { + var servers = parentGroup.RegisteredServers; + var groups = parentGroup.ServerGroups; + + // Convert to appropriate variables and return + var serverResults = new List(); + foreach (var s in servers) + { + serverResults.Add(new RegisteredServerResult + { + Name = s.Name, + ServerName = s.ServerName, + Description = s.Description, + ConnectionDetails = ConnectionServiceInstance.ParseConnectionString(s.ConnectionString), + RelativePath = s.KeyChain.Urn.SafeToString() + }); + } + + var groupsResults = new List(); + foreach (var s in groups) + { + groupsResults.Add(new RegisteredServerGroup + { + Name = s.Name, + Description = s.Description, + RelativePath = s.KeyChain.Urn.SafeToString() + }); + } + ListRegisteredServersResult result = new ListRegisteredServersResult() { RegisteredServersList = serverResults, RegisteredServerGroups = groupsResults }; + return result; + } + + #endregion + + /// + /// Internal for testing purposes only + /// + internal static ConnectionService ConnectionServiceInstance + { + get + { + if (connectionService == null) + { + connectionService = ConnectionService.Instance; + } + return connectionService; + } + set + { + connectionService = value; + } + } + + /// + /// Internal variable for testability + /// + internal Task CmsTask { get; set; } + } +} + diff --git a/src/Microsoft.SqlTools.ServiceLayer/Cms/Contracts/RegisterServerParams.cs b/src/Microsoft.SqlTools.ServiceLayer/Cms/Contracts/RegisterServerParams.cs new file mode 100644 index 0000000000..e2d30d7742 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Cms/Contracts/RegisterServerParams.cs @@ -0,0 +1,84 @@ +// +// 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.ServiceLayer.Connection.Contracts; + +namespace Microsoft.SqlTools.ServiceLayer.Cms.Contracts +{ + /// + /// Paramaters to create Top level Central Management Server + /// + public class CreateCentralManagementServerParams + { + public string RegisteredServerName { get; set; } + + public string RegisteredServerDescription { get; set; } + + public ConnectParams ConnectParams { get; set; } + } + + /// + /// Parameters to Add Registered Server to top level CMS + /// + public class AddRegisteredServerParams + { + public string RegisteredServerName { get; set; } + + public string RegisteredServerDescription { get; set; } + + public ConnectionDetails RegisteredServerConnectionDetails { get; set; } + + public string ParentOwnerUri { get; set; } + + public string RelativePath { get; set; } + } + + /// + /// Parameters to Add Server Group to top level CMS + /// + public class AddServerGroupParams + { + public string GroupName { get; set; } + + public string GroupDescription { get; set; } + + public string ParentOwnerUri { get; set; } + + public string RelativePath { get; set; } + } + + /// + /// Parameters to Remove Server Group from CMS + /// + public class RemoveServerGroupParams + { + public string GroupName { get; set; } + + public string ParentOwnerUri { get; set; } + + public string RelativePath { get; set; } + } + + /// + /// Paramaters to remove a Registered Server from CMS tree + /// + public class RemoveRegisteredServerParams + { + public string RegisteredServerName { get; set; } + + public string ParentOwnerUri { get; set; } + + public string RelativePath { get; set; } + } + + /// + /// Paramaters to list a Registered Server from CMS tree + /// + public class ListRegisteredServerParams + { + public string ParentOwnerUri { get; set; } + + public string RelativePath { get; set; } + } +} \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/Cms/Contracts/RegisteredServerRequest.cs b/src/Microsoft.SqlTools.ServiceLayer/Cms/Contracts/RegisteredServerRequest.cs new file mode 100644 index 0000000000..80f53bd582 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Cms/Contracts/RegisteredServerRequest.cs @@ -0,0 +1,46 @@ +// +// 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.Connection.Contracts; + +namespace Microsoft.SqlTools.ServiceLayer.Cms.Contracts +{ + + public class CreateCentralManagementServerRequest + { + public static readonly RequestType Type = + RequestType.Create("cms/createCms"); + } + + public class ListRegisteredServersRequest + { + public static readonly RequestType Type = + RequestType.Create("cms/listRegisteredServers"); + } + + public class AddRegisteredServerRequest + { + public static readonly RequestType Type = + RequestType.Create("cms/addRegisteredServer"); + } + + public class AddServerGroupRequest + { + public static readonly RequestType Type = + RequestType.Create("cms/addCmsServerGroup"); + } + + public class RemoveServerGroupRequest + { + public static readonly RequestType Type = + RequestType.Create("cms/removeCmsServerGroup"); + } + + public class RemoveRegisteredServerRequest + { + public static readonly RequestType Type = + RequestType.Create("cms/removeRegisteredServer"); + } +} \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/Cms/Contracts/RegisteredServersResult.cs b/src/Microsoft.SqlTools.ServiceLayer/Cms/Contracts/RegisteredServersResult.cs new file mode 100644 index 0000000000..bda5939b74 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/Cms/Contracts/RegisteredServersResult.cs @@ -0,0 +1,38 @@ +// +// 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.ServiceLayer.Connection.Contracts; +using System.Collections.Generic; + +namespace Microsoft.SqlTools.ServiceLayer.Cms.Contracts +{ + public class ListRegisteredServersResult + { + public List RegisteredServersList { get; set; } + + public List RegisteredServerGroups { get; set; } + } + + public class RegisteredServerResult + { + public string Name { get; set; } + + public string ServerName { get; set; } + + public string Description { get; set; } + + public string RelativePath { get; set; } + + public ConnectionDetails ConnectionDetails { get; set; } + } + + public class RegisteredServerGroup + { + public string Name { get; set; } + + public string Description { get; set; } + + public string RelativePath { get; set; } + } +} \ No newline at end of file diff --git a/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionService.cs b/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionService.cs index b0080b754d..70ec5b9329 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionService.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionService.cs @@ -1284,7 +1284,7 @@ public static SqlConnectionStringBuilder CreateConnectionStringBuilder(Connectio { await requestContext.SendResult(ParseConnectionString(connectionString)); } - catch (Exception e) + catch (Exception) { // If theres an error in the parse, it means we just can't parse, so we return undefined // rather than an error. diff --git a/src/Microsoft.SqlTools.ServiceLayer/HostLoader.cs b/src/Microsoft.SqlTools.ServiceLayer/HostLoader.cs index d9a129948d..7239dc66d7 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/HostLoader.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/HostLoader.cs @@ -10,6 +10,7 @@ using Microsoft.SqlTools.Hosting.Protocol; using Microsoft.SqlTools.ServiceLayer.Admin; using Microsoft.SqlTools.ServiceLayer.Agent; +using Microsoft.SqlTools.ServiceLayer.Cms; using Microsoft.SqlTools.ServiceLayer.Connection; using Microsoft.SqlTools.ServiceLayer.DacFx; using Microsoft.SqlTools.ServiceLayer.DisasterRecovery; @@ -114,6 +115,9 @@ private static void InitializeRequestHandlersAndServices(ServiceHost serviceHost DacFxService.Instance.InitializeService(serviceHost); serviceProvider.RegisterSingleService(DacFxService.Instance); + CmsService.Instance.InitializeService(serviceHost); + serviceProvider.RegisterSingleService(CmsService.Instance); + InitializeHostedServices(serviceProvider, serviceHost); serviceHost.ServiceProvider = serviceProvider; diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Cms/CmsServiceTests.cs b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Cms/CmsServiceTests.cs new file mode 100644 index 0000000000..c86f552cf5 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Cms/CmsServiceTests.cs @@ -0,0 +1,280 @@ +// +// 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.Cms; +using Microsoft.SqlTools.ServiceLayer.Cms.Contracts; +using Microsoft.SqlTools.ServiceLayer.Connection; +using Microsoft.SqlTools.ServiceLayer.Connection.Contracts; +using Microsoft.SqlTools.ServiceLayer.IntegrationTests.Utility; +using Microsoft.SqlTools.ServiceLayer.Test.Common; +using Moq; +using System; +using System.Threading.Tasks; +using Xunit; + +namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Cms +{ + public class CmsServiceTests + { + private ConnectParams CreateConnectParams() + { + ConnectParams connectParams = TestServiceProvider.Instance.ConnectionProfileService.GetConnectionParameters(TestServerType.OnPrem, "master"); + connectParams.OwnerUri = LiveConnectionHelper.GetTestSqlFile(); + connectParams.Connection.DatabaseName = null; + connectParams.Connection.DatabaseDisplayName = null; + + return connectParams; + } + + private async Task CreateAndConnectWithConnectParams() + { + ConnectParams connectParams = CreateConnectParams(); + ConnectionService connService = ConnectionService.Instance; + await connService.Connect(connectParams); + + return connectParams; + } + + [Fact] + private async void TestAddCMS() + { + string name = "TestAddCMS" + DateTime.Now.ToString(); + ConnectParams connectParams = CreateConnectParams(); + + // Prepare for list servers (may or may not have servers but will have listCmsServersResult) + var requestContext = new Mock>(); + requestContext.Setup((RequestContext x) => x.SendResult(It.Is((listCmsServersResult) => listCmsServersResult.RegisteredServersList != null))).Returns(Task.FromResult(new object())); + + CreateCentralManagementServerParams connectToCMS = new CreateCentralManagementServerParams + { + RegisteredServerName = name, + RegisteredServerDescription = "My Registered Test Server", + ConnectParams = connectParams + }; + + // Actual test after preparation start here + CmsService cmsService = CmsService.Instance; + + // Connect to CMS + await cmsService.HandleCreateCentralManagementServerRequest(connectToCMS, requestContext.Object); + await cmsService.CmsTask; + requestContext.VerifyAll(); + } + + [Fact] + private async void TestAddRemoveRegisteredServer() + { + string name = "TestAddRemoveRegisteredServer" + DateTime.Now.ToString(); + ConnectParams connectParams = await CreateAndConnectWithConnectParams(); + + // Prepare for Add Reg Server + var requestContext1 = new Mock>(); + requestContext1.Setup((RequestContext x) => x.SendResult(It.Is((result) => result == true))).Returns(Task.FromResult(new object())); + + AddRegisteredServerParams addRegServerParams = new AddRegisteredServerParams + { + RegisteredServerName = name, + RegisteredServerDescription = "My Registered Test Server", + ParentOwnerUri = connectParams.OwnerUri, + RelativePath = "RegisteredServersStore/ServerGroup[@Name='DatabaseEngineServerGroup']" //Top level + }; + + // Prepare for list servers + var requestContext2 = new Mock>(); + requestContext2.Setup((RequestContext x) => x.SendResult(It.Is((listCmsServersResult) => listCmsServersResult.RegisteredServersList.Find(p => p.Name.Contains(name)) != null))).Returns(Task.FromResult(new object())); + + ListRegisteredServerParams listServersParams = new ListRegisteredServerParams + { + ParentOwnerUri = connectParams.OwnerUri, + RelativePath = "RegisteredServersStore/ServerGroup[@Name='DatabaseEngineServerGroup']" + }; + + // Prepare for remove Server + var requestContext3 = new Mock>(); + requestContext1.Setup((RequestContext x) => x.SendResult(It.Is((result) => result == true))).Returns(Task.FromResult(new object())); + + RemoveRegisteredServerParams removeRegServerParams = new RemoveRegisteredServerParams + { + ParentOwnerUri = connectParams.OwnerUri, + RegisteredServerName = name, + RelativePath = string.Format("RegisteredServersStore/ServerGroup[@Name='DatabaseEngineServerGroup']/RegisteredServer[@Name='{0}']", name) + }; + + // Actual test after preparation start here + CmsService cmsService = CmsService.Instance; + + // Add Reg Server + await cmsService.HandleAddRegisteredServerRequest(addRegServerParams, requestContext1.Object); + await cmsService.CmsTask; + requestContext1.VerifyAll(); + + // List to validate + await cmsService.HandleListRegisteredServersRequest(listServersParams, requestContext2.Object); + await cmsService.CmsTask; + requestContext2.VerifyAll(); + + // Clean up + await cmsService.HandleRemoveRegisteredServerRequest(removeRegServerParams, requestContext3.Object); + await cmsService.CmsTask; + requestContext3.VerifyAll(); + } + + [Fact] + private async void TestAddRemoveServerGroup() + { + string name = "TestAddRemoveServerGroup" + DateTime.Now.ToString(); + ConnectParams connectParams = await CreateAndConnectWithConnectParams(); + + // Prepare for Server group add + var requestContext1 = new Mock>(); + requestContext1.Setup((RequestContext x) => x.SendResult(It.Is((result) => result == true))).Returns(Task.FromResult(new object())); + AddServerGroupParams addRegServerParams = new AddServerGroupParams + { + GroupName = name, + GroupDescription = "My Registered Test Server Group", + ParentOwnerUri = connectParams.OwnerUri, + RelativePath = null, + }; + + // prepare for Server group list + var requestContext2 = new Mock>(); + requestContext2.Setup((RequestContext x) => x.SendResult(It.Is((listCmsServersResult) => listCmsServersResult.RegisteredServerGroups.Find(p => p.Name.Contains(name)) != null))).Returns(Task.FromResult(new object())); + ListRegisteredServerParams listServersParams = new ListRegisteredServerParams + { + ParentOwnerUri = connectParams.OwnerUri, + RelativePath = null + }; + + // prepare for server group remove + var requestContext3 = new Mock>(); + requestContext1.Setup((RequestContext x) => x.SendResult(It.Is((result) => result == true))).Returns(Task.FromResult(new object())); + RemoveServerGroupParams removeRegServerParams = new RemoveServerGroupParams + { + ParentOwnerUri = connectParams.OwnerUri, + GroupName = name, + RelativePath = null + }; + + // Actual test start here + CmsService cmsService = CmsService.Instance; + + await cmsService.HandleAddServerGroupRequest(addRegServerParams, requestContext1.Object); + await cmsService.CmsTask; + requestContext1.VerifyAll(); + + await cmsService.HandleListRegisteredServersRequest(listServersParams, requestContext2.Object); + await cmsService.CmsTask; + requestContext2.VerifyAll(); + + await cmsService.HandleRemoveServerGroupRequest(removeRegServerParams, requestContext3.Object); + await cmsService.CmsTask; + requestContext3.VerifyAll(); + } + + [Fact] + private async void TestAddRemoveNestedGroup() + { + string name = "TestAddRemoveNestedGroup" + DateTime.Now.ToString(); + ConnectParams connectParams = await CreateAndConnectWithConnectParams(); + + // prepare for multi level server group add + var requestContextAdd = new Mock>(); + requestContextAdd.Setup((RequestContext x) => x.SendResult(It.Is((result) => result == true))).Returns(Task.FromResult(new object())); + + AddServerGroupParams addRegServerParams1 = new AddServerGroupParams + { + GroupName = name + "_level1", + GroupDescription = "My Registered Test Server Group Level 1", + ParentOwnerUri = connectParams.OwnerUri, + RelativePath = null, // can do with null on level 1 + }; + + AddServerGroupParams addRegServerParams2 = new AddServerGroupParams + { + GroupName = name + "_level2", + GroupDescription = "My Registered Test Server Group Level 2", + ParentOwnerUri = connectParams.OwnerUri, + RelativePath = string.Format("RegisteredServersStore/ServerGroup[@Name='DatabaseEngineServerGroup']/ServerGroup[@Name='{0}']", name + "_level1") // parent URN + }; + + AddServerGroupParams addRegServerParams3 = new AddServerGroupParams + { + GroupName = name + "_level3", + GroupDescription = "My Registered Test Server Group Level 3", + ParentOwnerUri = connectParams.OwnerUri, + RelativePath = string.Format("RegisteredServersStore/ServerGroup[@Name='DatabaseEngineServerGroup']/ServerGroup[@Name='{0}']/ServerGroup[@Name='{1}']", name + "_level1", name + "_level2") // parent URN + }; + + // prepare for multi level server group list + + var requestContextList1 = new Mock>(); + requestContextList1.Setup((RequestContext x) => x.SendResult(It.Is((listCmsServersResult) => listCmsServersResult.RegisteredServerGroups.Find(p => p.Name.Contains(name + "_level3")) != null))).Returns(Task.FromResult(new object())); + + var requestContextList2 = new Mock>(); + requestContextList2.Setup((RequestContext x) => x.SendResult(It.Is((listCmsServersResult) => listCmsServersResult.RegisteredServerGroups.Find(p => p.Name.Contains(name + "_level3")) == null))).Returns(Task.FromResult(new object())); + + ListRegisteredServerParams listServersParams = new ListRegisteredServerParams + { + ParentOwnerUri = connectParams.OwnerUri, + RelativePath = string.Format("RegisteredServersStore/ServerGroup[@Name='DatabaseEngineServerGroup']/ServerGroup[@Name='{0}']/ServerGroup[@Name='{1}']", name + "_level1", name + "_level2") // parent URN + }; + + // prepare for multi level server group remove at level 3 and then at level 1 + var requestContextRemove = new Mock>(); + requestContextRemove.Setup((RequestContext x) => x.SendResult(It.Is((result) => result == true))).Returns(Task.FromResult(new object())); + + RemoveServerGroupParams removeRegServerParams = new RemoveServerGroupParams + { + ParentOwnerUri = connectParams.OwnerUri, + GroupName = name + "_level3", + RelativePath = string.Format("RegisteredServersStore/ServerGroup[@Name='DatabaseEngineServerGroup']/ServerGroup[@Name='{0}']/ServerGroup[@Name='{1}']/ServerGroup[@Name='{2}']", name + "_level1", name + "_level2", name + "_level3") // own URN + }; + + RemoveServerGroupParams removeRegServerParamsCleanup = new RemoveServerGroupParams + { + ParentOwnerUri = connectParams.OwnerUri, + GroupName = name + "_level1", + RelativePath = string.Format("RegisteredServersStore/ServerGroup[@Name='DatabaseEngineServerGroup']/ServerGroup[@Name='{0}']", name + "_level1") // own URN + }; + + // Actual test starts here + CmsService cmsService = CmsService.Instance; + + // Add three levels + await cmsService.HandleAddServerGroupRequest(addRegServerParams1, requestContextAdd.Object); + await cmsService.CmsTask; + requestContextAdd.VerifyAll(); + + await cmsService.HandleAddServerGroupRequest(addRegServerParams2, requestContextAdd.Object); + await cmsService.CmsTask; + requestContextAdd.VerifyAll(); + + await cmsService.HandleAddServerGroupRequest(addRegServerParams3, requestContextAdd.Object); + await cmsService.CmsTask; + requestContextAdd.VerifyAll(); + + // List Level 2 to find level three + await cmsService.HandleListRegisteredServersRequest(listServersParams, requestContextList1.Object); + await cmsService.CmsTask; + requestContextList1.VerifyAll(); + + // Remove level 3 + await cmsService.HandleRemoveServerGroupRequest(removeRegServerParams, requestContextRemove.Object); + await cmsService.CmsTask; + requestContextRemove.VerifyAll(); + + // List Level 2 to validate Level 3 removal + await cmsService.HandleListRegisteredServersRequest(listServersParams, requestContextList2.Object); + await cmsService.CmsTask; + requestContextList2.VerifyAll(); + + // Clean up - Remove Level 1 + await cmsService.HandleRemoveServerGroupRequest(removeRegServerParamsCleanup, requestContextRemove.Object); + await cmsService.CmsTask; + requestContextRemove.VerifyAll(); + } + + } +}