From 0326caeb568a18617e1e10351f0dc328c12d1cf2 Mon Sep 17 00:00:00 2001 From: AdityaNPL Date: Tue, 26 Jan 2021 20:18:17 +0000 Subject: [PATCH 01/14] fix(linux_dns_issue): POC --- src/SmoFacade/Listener.cs | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/src/SmoFacade/Listener.cs b/src/SmoFacade/Listener.cs index 150d3d8..c94ad4b 100755 --- a/src/SmoFacade/Listener.cs +++ b/src/SmoFacade/Listener.cs @@ -5,7 +5,8 @@ namespace AgDatabaseMove.SmoFacade using System.Data.SqlClient; using System.Linq; using System.Net; - using System.Threading.Tasks; + using System.Runtime.InteropServices; + using System.Threading.Tasks; internal interface IListener : IDisposable @@ -134,15 +135,27 @@ private static Server AgListenerNameToServer(ref SqlConnectionStringBuilder conn string credentialName) { var parts = agInstanceName.Split('\\'); - if(parts.Length == 1) - connBuilder.DataSource = Dns.GetHostEntry(agInstanceName).HostName; - else if(parts.Length == 2) - // NamedInstances: chop instance name, resolve DNS, slap instance name back on! - connBuilder.DataSource = $"{Dns.GetHostEntry(parts[0]).HostName}\\{parts[1]}"; - else - throw new ArgumentException($"agInstanceName param {agInstanceName} cannot be resolved by DNS"); + if (parts.Length == 1) + connBuilder.DataSource = ResolveDnsAndGetHostName(connBuilder.DataSource, agInstanceName); + else if (parts.Length == 2) + // NamedInstances: chop instance name, resolve DNS, slap instance name back on! + connBuilder.DataSource = $"{ResolveDnsAndGetHostName(connBuilder.DataSource, parts[0])}\\{parts[1]}"; + else + throw new ArgumentException($"agInstanceName param {agInstanceName} cannot be resolved by DNS"); return new Server(connBuilder.ToString(), credentialName); } + + private static string ResolveDnsAndGetHostName(string hostUrl, string instance) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && !instance.Contains('.')) + { + string[] urlFragments = hostUrl.Split('.'); + urlFragments[0] = ""; + instance += string.Join(".", urlFragments); + } + var res = Dns.GetHostEntry(instance).HostName; + return res; + } } } \ No newline at end of file From 890c29316694be16c1850ecaa32bb92eb5cb06ce Mon Sep 17 00:00:00 2001 From: AdityaNPL Date: Wed, 27 Jan 2021 11:56:27 +0000 Subject: [PATCH 02/14] fix(linux_dns_issue): compatible with dotnet 4.7 + summary docs --- src/SmoFacade/Listener.cs | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/SmoFacade/Listener.cs b/src/SmoFacade/Listener.cs index c94ad4b..f1e9460 100755 --- a/src/SmoFacade/Listener.cs +++ b/src/SmoFacade/Listener.cs @@ -5,8 +5,7 @@ namespace AgDatabaseMove.SmoFacade using System.Data.SqlClient; using System.Linq; using System.Net; - using System.Runtime.InteropServices; - using System.Threading.Tasks; + using System.Threading.Tasks; internal interface IListener : IDisposable @@ -146,16 +145,23 @@ private static Server AgListenerNameToServer(ref SqlConnectionStringBuilder conn return new Server(connBuilder.ToString(), credentialName); } - private static string ResolveDnsAndGetHostName(string hostUrl, string instance) + /// + /// Resolves "instanceName" to a FQDNS name + /// However on Unix OS, "instanceName" should be a complete domain - "{sub domain}.{second level domain}.{...}.{top level domain}" (eg: "xyz.abc.def.com") + /// Therefore, append from the {second-level} -> {top-level} domains from the "hostDomain" to the "instanceName" to make it a complete domain if it is not already + /// + /// The complete domain for the SQL server + /// The specific name of an instance within the server + private static string ResolveDnsAndGetHostName(string hostDomain, string instanceName) { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && !instance.Contains('.')) + if (Environment.OSVersion.Platform == PlatformID.Unix && !instanceName.Contains('.')) { - string[] urlFragments = hostUrl.Split('.'); - urlFragments[0] = ""; - instance += string.Join(".", urlFragments); + string[] domainFragments = hostDomain.Split('.'); + domainFragments[0] = ""; + instanceName += string.Join(".", domainFragments); } - var res = Dns.GetHostEntry(instance).HostName; - return res; + return Dns.GetHostEntry(instanceName).HostName; + } } } \ No newline at end of file From 483e636490ab3af84532ca6a46cde0fce51d6922 Mon Sep 17 00:00:00 2001 From: AdityaNPL Date: Wed, 27 Jan 2021 11:59:50 +0000 Subject: [PATCH 03/14] fix(linux_dns_issue): whitespace --- src/SmoFacade/Listener.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/SmoFacade/Listener.cs b/src/SmoFacade/Listener.cs index f1e9460..d7b4609 100755 --- a/src/SmoFacade/Listener.cs +++ b/src/SmoFacade/Listener.cs @@ -134,13 +134,13 @@ private static Server AgListenerNameToServer(ref SqlConnectionStringBuilder conn string credentialName) { var parts = agInstanceName.Split('\\'); - if (parts.Length == 1) - connBuilder.DataSource = ResolveDnsAndGetHostName(connBuilder.DataSource, agInstanceName); - else if (parts.Length == 2) - // NamedInstances: chop instance name, resolve DNS, slap instance name back on! - connBuilder.DataSource = $"{ResolveDnsAndGetHostName(connBuilder.DataSource, parts[0])}\\{parts[1]}"; - else - throw new ArgumentException($"agInstanceName param {agInstanceName} cannot be resolved by DNS"); + if(parts.Length == 1) + connBuilder.DataSource = ResolveDnsAndGetHostName(connBuilder.DataSource, agInstanceName); + else if(parts.Length == 2) + // NamedInstances: chop instance name, resolve DNS, slap instance name back on! + connBuilder.DataSource = $"{ResolveDnsAndGetHostName(connBuilder.DataSource, parts[0])}\\{parts[1]}"; + else + throw new ArgumentException($"agInstanceName param {agInstanceName} cannot be resolved by DNS"); return new Server(connBuilder.ToString(), credentialName); } From a76a81bbab7c9a2525d28979693f895e9f20c6f6 Mon Sep 17 00:00:00 2001 From: AdityaNPL Date: Wed, 27 Jan 2021 12:01:19 +0000 Subject: [PATCH 04/14] fix(linux_dns_issue): more whitespace --- src/SmoFacade/Listener.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/SmoFacade/Listener.cs b/src/SmoFacade/Listener.cs index d7b4609..b8f059e 100755 --- a/src/SmoFacade/Listener.cs +++ b/src/SmoFacade/Listener.cs @@ -134,13 +134,13 @@ private static Server AgListenerNameToServer(ref SqlConnectionStringBuilder conn string credentialName) { var parts = agInstanceName.Split('\\'); - if(parts.Length == 1) - connBuilder.DataSource = ResolveDnsAndGetHostName(connBuilder.DataSource, agInstanceName); - else if(parts.Length == 2) - // NamedInstances: chop instance name, resolve DNS, slap instance name back on! - connBuilder.DataSource = $"{ResolveDnsAndGetHostName(connBuilder.DataSource, parts[0])}\\{parts[1]}"; - else - throw new ArgumentException($"agInstanceName param {agInstanceName} cannot be resolved by DNS"); + if(parts.Length == 1) + connBuilder.DataSource = ResolveDnsAndGetHostName(connBuilder.DataSource, agInstanceName); + else if(parts.Length == 2) + // NamedInstances: chop instance name, resolve DNS, slap instance name back on! + connBuilder.DataSource = $"{ResolveDnsAndGetHostName(connBuilder.DataSource, parts[0])}\\{parts[1]}"; + else + throw new ArgumentException($"agInstanceName param {agInstanceName} cannot be resolved by DNS"); return new Server(connBuilder.ToString(), credentialName); } From fda9a11826c67865ddfaf0196fafdb8c2c449ad0 Mon Sep 17 00:00:00 2001 From: AdityaNPL Date: Wed, 27 Jan 2021 17:20:23 +0000 Subject: [PATCH 05/14] feat(linux_dns_issue): Handling ports and named instances --- src/SmoFacade/Listener.cs | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/SmoFacade/Listener.cs b/src/SmoFacade/Listener.cs index b8f059e..c9f2938 100755 --- a/src/SmoFacade/Listener.cs +++ b/src/SmoFacade/Listener.cs @@ -154,14 +154,33 @@ private static Server AgListenerNameToServer(ref SqlConnectionStringBuilder conn /// The specific name of an instance within the server private static string ResolveDnsAndGetHostName(string hostDomain, string instanceName) { + Tuple domain_port = SplitDomainPort(hostDomain); if (Environment.OSVersion.Platform == PlatformID.Unix && !instanceName.Contains('.')) { - string[] domainFragments = hostDomain.Split('.'); + string[] domainFragments = domain_port.Item1.Split('.'); domainFragments[0] = ""; instanceName += string.Join(".", domainFragments); } - return Dns.GetHostEntry(instanceName).HostName; + return Dns.GetHostEntry(instanceName).HostName+domain_port.Item2; } + private static Tuple SplitDomainPort(string hostDomain) + { + string domian = hostDomain; + string port = ""; + if (hostDomain.Contains(',')) + { + string[] fragments = hostDomain.Split(new char[] {','}, 2); + domian = fragments[0]; + port = ","+fragments[1]; + } + else if (hostDomain.Contains('\\')) + { + string[] fragments = hostDomain.Split(new char[] {'\\'}, 2); + domian = fragments[0]; + port = "\\"+fragments[1]; + } + return new Tuple(domian, port); + } } } \ No newline at end of file From 8e22c6b256202f04ccb227363e23d1aad852eae0 Mon Sep 17 00:00:00 2001 From: AdityaNPL Date: Wed, 27 Jan 2021 18:45:32 +0000 Subject: [PATCH 06/14] refactor(linux_dns_issue): some renames + removed the check to see if 'agReplicaInstanceName' is already FQDN --- src/SmoFacade/Listener.cs | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/SmoFacade/Listener.cs b/src/SmoFacade/Listener.cs index c9f2938..53cd018 100755 --- a/src/SmoFacade/Listener.cs +++ b/src/SmoFacade/Listener.cs @@ -146,37 +146,37 @@ private static Server AgListenerNameToServer(ref SqlConnectionStringBuilder conn } /// - /// Resolves "instanceName" to a FQDNS name - /// However on Unix OS, "instanceName" should be a complete domain - "{sub domain}.{second level domain}.{...}.{top level domain}" (eg: "xyz.abc.def.com") - /// Therefore, append from the {second-level} -> {top-level} domains from the "hostDomain" to the "instanceName" to make it a complete domain if it is not already + /// Resolves "agReplicaInstanceName" to a FQDNS name + /// However on Unix OS, "agReplicaInstanceName" should be a complete domain - "{sub domain}.{second level domain}.{...}.{top level domain}" (eg: "xyz.abc.def.com") + /// Therefore, we use {second-level} -> {top-level} part of the domain from "agListenerDomain" and append it to "agReplicaInstanceName" /// - /// The complete domain for the SQL server - /// The specific name of an instance within the server - private static string ResolveDnsAndGetHostName(string hostDomain, string instanceName) + /// The complete domain for the SQL server AG listener + /// The specific name of a replica instance within the AG + private static string ResolveDnsAndGetHostName(string agListenerDomain, string agReplicaInstanceName) { - Tuple domain_port = SplitDomainPort(hostDomain); - if (Environment.OSVersion.Platform == PlatformID.Unix && !instanceName.Contains('.')) + Tuple domain_port = SplitDomainPort(agListenerDomain); + if (Environment.OSVersion.Platform == PlatformID.Unix) { string[] domainFragments = domain_port.Item1.Split('.'); domainFragments[0] = ""; - instanceName += string.Join(".", domainFragments); + agReplicaInstanceName += string.Join(".", domainFragments); } - return Dns.GetHostEntry(instanceName).HostName+domain_port.Item2; + return Dns.GetHostEntry(agReplicaInstanceName).HostName+domain_port.Item2; } - private static Tuple SplitDomainPort(string hostDomain) + private static Tuple SplitDomainPort(string domainAndPort) { - string domian = hostDomain; + string domian = domainAndPort; string port = ""; - if (hostDomain.Contains(',')) + if (domainAndPort.Contains(',')) { - string[] fragments = hostDomain.Split(new char[] {','}, 2); + string[] fragments = domainAndPort.Split(new char[] {','}, 2); domian = fragments[0]; port = ","+fragments[1]; } - else if (hostDomain.Contains('\\')) + else if (domainAndPort.Contains('\\')) { - string[] fragments = hostDomain.Split(new char[] {'\\'}, 2); + string[] fragments = domainAndPort.Split(new char[] {'\\'}, 2); domian = fragments[0]; port = "\\"+fragments[1]; } From 51a01e1f584cc58f725b61ffc51652cd448f9a9b Mon Sep 17 00:00:00 2001 From: AdityaNPL Date: Thu, 28 Jan 2021 17:40:10 +0000 Subject: [PATCH 07/14] fix(linux_dns_issue): moved named instance handling to 'SplitDomainPort()' + PR suggestions --- src/SmoFacade/Listener.cs | 61 ++++++++++++++++++++++++--------------- 1 file changed, 37 insertions(+), 24 deletions(-) diff --git a/src/SmoFacade/Listener.cs b/src/SmoFacade/Listener.cs index 53cd018..94c175c 100755 --- a/src/SmoFacade/Listener.cs +++ b/src/SmoFacade/Listener.cs @@ -133,16 +133,15 @@ private static string AgListenerName(string dataSource) private static Server AgListenerNameToServer(ref SqlConnectionStringBuilder connBuilder, string agInstanceName, string credentialName) { - var parts = agInstanceName.Split('\\'); - if(parts.Length == 1) + try + { connBuilder.DataSource = ResolveDnsAndGetHostName(connBuilder.DataSource, agInstanceName); - else if(parts.Length == 2) - // NamedInstances: chop instance name, resolve DNS, slap instance name back on! - connBuilder.DataSource = $"{ResolveDnsAndGetHostName(connBuilder.DataSource, parts[0])}\\{parts[1]}"; - else - throw new ArgumentException($"agInstanceName param {agInstanceName} cannot be resolved by DNS"); - - return new Server(connBuilder.ToString(), credentialName); + return new Server(connBuilder.ToString(), credentialName); + } + catch (Exception e) + { + throw new ArgumentException($"agInstanceName param {agInstanceName} cannot be resolved by DNS", e); + } } /// @@ -154,33 +153,47 @@ private static Server AgListenerNameToServer(ref SqlConnectionStringBuilder conn /// The specific name of a replica instance within the AG private static string ResolveDnsAndGetHostName(string agListenerDomain, string agReplicaInstanceName) { - Tuple domain_port = SplitDomainPort(agListenerDomain); - if (Environment.OSVersion.Platform == PlatformID.Unix) + // sometimes instances/listners have ports and named instances. Therefore, we strip them off before calling DNS.GetHostEntry() and then add them back to the result + (string listenerDomain, string listenerPort) = SplitDomainPort(agListenerDomain); + (string instanceDomain, string instancePort) = SplitDomainPort(agReplicaInstanceName); + // we want to add back instancePort if it exists, otherwise the server port + string portToUse = instancePort ?? listenerPort; + try { - string[] domainFragments = domain_port.Item1.Split('.'); - domainFragments[0] = ""; - agReplicaInstanceName += string.Join(".", domainFragments); + return $"{Dns.GetHostEntry(instanceDomain).HostName}{portToUse}"; } - return Dns.GetHostEntry(agReplicaInstanceName).HostName+domain_port.Item2; + catch (System.Net.Sockets.SocketException e) + { + Console.WriteLine($"Unable to get dns for instance '{instanceDomain}'\n{e}"); + + // Re-try with full domain appended to the instanceDomain by stripping off domain from listner and appending to instance + // eg: if listner is "abc.def.ghi" we want to append ".def.ghi" to the instance + string[] domainFragments = listenerDomain.Split('.'); + domainFragments[0] = ""; + string instanceDomainFull = $"{instanceDomain}{string.Join(".", domainFragments)}"; + Console.WriteLine($"Retrying with full domain '{instanceDomainFull}"); + return $"{Dns.GetHostEntry(instanceDomainFull).HostName}{portToUse}"; + } } - private static Tuple SplitDomainPort(string domainAndPort) + + private static (string domain, string port) SplitDomainPort(string domainAndPort) { - string domian = domainAndPort; - string port = ""; + string domain = domainAndPort; + string port = null; if (domainAndPort.Contains(',')) { string[] fragments = domainAndPort.Split(new char[] {','}, 2); - domian = fragments[0]; - port = ","+fragments[1]; + domain = fragments[0]; + port = $",{fragments[1]}"; } - else if (domainAndPort.Contains('\\')) + else if (domainAndPort.Contains('\\')) // handling named instances the same way as ports { string[] fragments = domainAndPort.Split(new char[] {'\\'}, 2); - domian = fragments[0]; - port = "\\"+fragments[1]; + domain = fragments[0]; + port = $"\\{fragments[1]}"; } - return new Tuple(domian, port); + return (domain, port); } } } \ No newline at end of file From ced0e8cca4c5614779ca569fd3b8b7ba8416c746 Mon Sep 17 00:00:00 2001 From: AdityaNPL Date: Wed, 3 Feb 2021 18:50:29 +0000 Subject: [PATCH 08/14] refactor(linux_dns_issue): PR suggestions + comments + renames --- src/SmoFacade/Listener.cs | 80 +++++++++++++++++++-------------------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/src/SmoFacade/Listener.cs b/src/SmoFacade/Listener.cs index 94c175c..40b1433 100755 --- a/src/SmoFacade/Listener.cs +++ b/src/SmoFacade/Listener.cs @@ -69,12 +69,12 @@ public Listener(SqlConnectionStringBuilder connectionStringBuilder, string crede var secondaryNames = availabilityGroup.Replicas.Where(l => l != primaryName); // Connect to each server instance - Primary = AgListenerNameToServer(ref connectionStringBuilder, primaryName, credentialName); + Primary = AgInstanceNameToServer(ref connectionStringBuilder, primaryName, credentialName); AvailabilityGroup = Primary.AvailabilityGroups.Single(ag => ag.Name == availabilityGroup.Name); _secondaries = new List(); foreach(var secondaryName in secondaryNames) - _secondaries.Add(AgListenerNameToServer(ref connectionStringBuilder, + _secondaries.Add(AgInstanceNameToServer(ref connectionStringBuilder, secondaryName, credentialName)); } @@ -130,12 +130,12 @@ private static string AgListenerName(string dataSource) return dotIndex >= 0 ? dataSource.Remove(dotIndex) : dataSource; } - private static Server AgListenerNameToServer(ref SqlConnectionStringBuilder connBuilder, string agInstanceName, + private static Server AgInstanceNameToServer(ref SqlConnectionStringBuilder connBuilder, string agInstanceName, string credentialName) { try { - connBuilder.DataSource = ResolveDnsAndGetHostName(connBuilder.DataSource, agInstanceName); + connBuilder.DataSource = ResolveDnsHostNameForInstance(agInstanceName, connBuilder.DataSource); return new Server(connBuilder.ToString(), credentialName); } catch (Exception e) @@ -145,55 +145,55 @@ private static Server AgListenerNameToServer(ref SqlConnectionStringBuilder conn } /// - /// Resolves "agReplicaInstanceName" to a FQDNS name - /// However on Unix OS, "agReplicaInstanceName" should be a complete domain - "{sub domain}.{second level domain}.{...}.{top level domain}" (eg: "xyz.abc.def.com") - /// Therefore, we use {second-level} -> {top-level} part of the domain from "agListenerDomain" and append it to "agReplicaInstanceName" + /// Resolves 'agReplicaInstanceName' to a FQDN + /// However on Unix OS, when 'val' in 'Dns.GetHostEntry(val)' is not a complete domain (i.e is just "abc", instead of "abc.def.com"), it fails intermittently + /// Therefore, if dns lookup on just the instance name fails, we retry after appending the domain fragments from the listener to the instance name /// - /// The complete domain for the SQL server AG listener - /// The specific name of a replica instance within the AG - private static string ResolveDnsAndGetHostName(string agListenerDomain, string agReplicaInstanceName) + /// The name for an instance within the AG (for which we are trying to get the FQDN) + /// The complete domain for the AG listener + private static string ResolveDnsHostNameForInstance(string agReplicaInstanceName, string agListenerDomain) { - // sometimes instances/listners have ports and named instances. Therefore, we strip them off before calling DNS.GetHostEntry() and then add them back to the result - (string listenerDomain, string listenerPort) = SplitDomainPort(agListenerDomain); - (string instanceDomain, string instancePort) = SplitDomainPort(agReplicaInstanceName); - // we want to add back instancePort if it exists, otherwise the server port - string portToUse = instancePort ?? listenerPort; + // Sometimes instances and listeners have ports(or "named instances") + // Therefore, we strip them off before calling DNS.GetHostEntry() and then add them back to the result + var (listenerDomain, listenerPort) = SplitDomainAndPort(agListenerDomain); + var (instanceName, instancePort) = SplitDomainAndPort(agReplicaInstanceName); + // preference is to add back 'instancePort' over 'listenerPort' (in almost all cases they should be identical) + var port = instancePort ?? listenerPort; + try { - return $"{Dns.GetHostEntry(instanceDomain).HostName}{portToUse}"; + return $"{Dns.GetHostEntry(instanceName).HostName}{port}"; } - catch (System.Net.Sockets.SocketException e) + catch (System.Net.Sockets.SocketException) { - Console.WriteLine($"Unable to get dns for instance '{instanceDomain}'\n{e}"); - - // Re-try with full domain appended to the instanceDomain by stripping off domain from listner and appending to instance - // eg: if listner is "abc.def.ghi" we want to append ".def.ghi" to the instance - string[] domainFragments = listenerDomain.Split('.'); - domainFragments[0] = ""; - string instanceDomainFull = $"{instanceDomain}{string.Join(".", domainFragments)}"; - - Console.WriteLine($"Retrying with full domain '{instanceDomainFull}"); - return $"{Dns.GetHostEntry(instanceDomainFull).HostName}{portToUse}"; + // Re-try by appending the domain fragments from listener to the instance name + // However, we don't need the listener's "host name" (first fragment), so we need to strip that off + // eg: if listener is "abc.def.ghi" we want to append only ".def.ghi" to the instance name + var listenerDomainFragments = listenerDomain.Split('.'); + listenerDomainFragments[0] = null; + var instanceDomain = $"{instanceName}{string.Join(".", listenerDomainFragments)}"; + + return $"{Dns.GetHostEntry(instanceDomain).HostName}{port}"; } } - private static (string domain, string port) SplitDomainPort(string domainAndPort) + // This function handles named instances ("\") in the same way as ports + private static (string, string) SplitDomainAndPort(string domainAndPort) { - string domain = domainAndPort; - string port = null; - if (domainAndPort.Contains(',')) - { - string[] fragments = domainAndPort.Split(new char[] {','}, 2); - domain = fragments[0]; - port = $",{fragments[1]}"; - } - else if (domainAndPort.Contains('\\')) // handling named instances the same way as ports + var domain = domainAndPort; + var splitValue = domainAndPort.Contains(',') ? "," : domainAndPort.Contains('\\') ? "\\" : null; + + if(splitValue == null) { - string[] fragments = domainAndPort.Split(new char[] {'\\'}, 2); - domain = fragments[0]; - port = $"\\{fragments[1]}"; + return (domain, null); } + + var fragments = domainAndPort.Split(splitValue.ToCharArray(), 2); + domain = fragments[0]; + var port = $"{splitValue}{fragments[1]}"; + return (domain, port); } + } } \ No newline at end of file From 8e9a165a3f0f335ff768e6b728b8072f76ae0223 Mon Sep 17 00:00:00 2001 From: AdityaNPL Date: Fri, 5 Feb 2021 13:40:56 +0000 Subject: [PATCH 09/14] test(Listener): New tests for 'SplitDomainAndPort()' --- src/SmoFacade/Listener.cs | 19 +++---- .../SmoFacadeListenerTest.cs | 49 +++++++++++++++++++ 2 files changed, 59 insertions(+), 9 deletions(-) create mode 100644 tests/AgDatabaseMove.Unit/SmoFacadeListenerTest.cs diff --git a/src/SmoFacade/Listener.cs b/src/SmoFacade/Listener.cs index 40b1433..9f253f9 100755 --- a/src/SmoFacade/Listener.cs +++ b/src/SmoFacade/Listener.cs @@ -1,13 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Data.SqlClient; +using System.Linq; +using System.Net; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; + +[assembly: InternalsVisibleTo("AgDatabaseMove.Unit")] namespace AgDatabaseMove.SmoFacade { - using System; - using System.Collections.Generic; - using System.Data.SqlClient; - using System.Linq; - using System.Net; - using System.Threading.Tasks; - - internal interface IListener : IDisposable { /// @@ -178,7 +179,7 @@ private static string ResolveDnsHostNameForInstance(string agReplicaInstanceName } // This function handles named instances ("\") in the same way as ports - private static (string, string) SplitDomainAndPort(string domainAndPort) + internal static (string, string) SplitDomainAndPort(string domainAndPort) { var domain = domainAndPort; var splitValue = domainAndPort.Contains(',') ? "," : domainAndPort.Contains('\\') ? "\\" : null; diff --git a/tests/AgDatabaseMove.Unit/SmoFacadeListenerTest.cs b/tests/AgDatabaseMove.Unit/SmoFacadeListenerTest.cs new file mode 100644 index 0000000..7083043 --- /dev/null +++ b/tests/AgDatabaseMove.Unit/SmoFacadeListenerTest.cs @@ -0,0 +1,49 @@ +using System.Collections.Generic; +using Xunit; + +namespace AgDatabaseMove.Unit +{ + public class SmoFacadeListenerTest + { + private const string DOMAIN = "abc.def.ghi"; + private const string HOST = "abc"; + + // {input, expectedDomain, expectedPort (can be null)} + public static IEnumerable ValidPorts => new List { + new object[] {$"{DOMAIN}", DOMAIN}, + new object[] {$"{DOMAIN},123", DOMAIN, ",123" }, + new object[] {$"{DOMAIN}\\SQL", DOMAIN, "\\SQL" }, + + new object[] {$"{HOST}", HOST}, + new object[] {$"{HOST},123", HOST, ",123" }, + new object[] {$"{HOST}\\SQL", HOST, "\\SQL" } + }; + + [Theory] + [MemberData(nameof(ValidPorts))] + public void ValidPortTests(string input, string expectedDomain, string expectedPort=null) + { + var (domain, port) = SmoFacade.Listener.SplitDomainAndPort(input); + Assert.Equal(domain, expectedDomain); + Assert.Equal(port, expectedPort); + } + + // {input, expectedDomain, expectedPort (can be null)} + public static IEnumerable InvalidPorts => new List { + new object[] {$"{DOMAIN}:123", $"{DOMAIN}:123" }, + new object[] {$"{DOMAIN}:SQL", $"{DOMAIN}:SQL" }, + + new object[] {$"{HOST}:123", $"{HOST}:123" }, + new object[] {$"{HOST}:SQL", $"{HOST}:SQL" }, + }; + + [Theory] + [MemberData(nameof(InvalidPorts))] + public void InvalidPortTests(string input, string expectedDomain, string expectedPort=null) + { + var (domain, port) = SmoFacade.Listener.SplitDomainAndPort(input); + Assert.Equal(domain, expectedDomain); + Assert.Equal(port, expectedPort); + } + } +} From 3067f1cda1b737ee14f3505faeb616504011db10 Mon Sep 17 00:00:00 2001 From: AdityaNPL Date: Fri, 5 Feb 2021 13:44:29 +0000 Subject: [PATCH 10/14] refactor(Listener): Moving using statements back inside namespace --- src/SmoFacade/Listener.cs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/SmoFacade/Listener.cs b/src/SmoFacade/Listener.cs index 9f253f9..727d7c1 100755 --- a/src/SmoFacade/Listener.cs +++ b/src/SmoFacade/Listener.cs @@ -1,14 +1,13 @@ -using System; -using System.Collections.Generic; -using System.Data.SqlClient; -using System.Linq; -using System.Net; -using System.Runtime.CompilerServices; -using System.Threading.Tasks; - -[assembly: InternalsVisibleTo("AgDatabaseMove.Unit")] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("AgDatabaseMove.Unit")] namespace AgDatabaseMove.SmoFacade { + using System; + using System.Collections.Generic; + using System.Data.SqlClient; + using System.Linq; + using System.Net; + using System.Threading.Tasks; + internal interface IListener : IDisposable { /// From 437c8bac36d0c03b7429db062071d1accaa3fdfc Mon Sep 17 00:00:00 2001 From: AdityaNPL Date: Fri, 5 Feb 2021 14:26:28 +0000 Subject: [PATCH 11/14] fix(linux_dns_issue): prefer adding back ports over named instances --- src/SmoFacade/Listener.cs | 22 ++++++- .../SmoFacadeListenerTest.cs | 57 +++++++++++++++---- 2 files changed, 66 insertions(+), 13 deletions(-) diff --git a/src/SmoFacade/Listener.cs b/src/SmoFacade/Listener.cs index 727d7c1..3b3098b 100755 --- a/src/SmoFacade/Listener.cs +++ b/src/SmoFacade/Listener.cs @@ -8,6 +8,7 @@ namespace AgDatabaseMove.SmoFacade using System.Net; using System.Threading.Tasks; + internal interface IListener : IDisposable { /// @@ -157,8 +158,7 @@ private static string ResolveDnsHostNameForInstance(string agReplicaInstanceName // Therefore, we strip them off before calling DNS.GetHostEntry() and then add them back to the result var (listenerDomain, listenerPort) = SplitDomainAndPort(agListenerDomain); var (instanceName, instancePort) = SplitDomainAndPort(agReplicaInstanceName); - // preference is to add back 'instancePort' over 'listenerPort' (in almost all cases they should be identical) - var port = instancePort ?? listenerPort; + var port = GetPreferredPort(instancePort, listenerPort); try { @@ -177,8 +177,24 @@ private static string ResolveDnsHostNameForInstance(string agReplicaInstanceName } } + // first preference is to add back port over named instances + // (port is TCP/IP standard while named instance is only SQL Server standard) + // If both are same, prioritise 'instancePort' over 'listenerPort' (in almost all cases they should be identical) + internal static string GetPreferredPort(string instancePort, string listenerPort) + { + if (string.IsNullOrEmpty(instancePort) || string.IsNullOrEmpty(listenerPort)) + { + return (instancePort ?? listenerPort); + } + if (instancePort.StartsWith("\\")) + { + return (listenerPort.StartsWith(",")? listenerPort: instancePort); + } + return instancePort; + } + // This function handles named instances ("\") in the same way as ports - internal static (string, string) SplitDomainAndPort(string domainAndPort) + internal static (string domain, string port) SplitDomainAndPort(string domainAndPort) { var domain = domainAndPort; var splitValue = domainAndPort.Contains(',') ? "," : domainAndPort.Contains('\\') ? "\\" : null; diff --git a/tests/AgDatabaseMove.Unit/SmoFacadeListenerTest.cs b/tests/AgDatabaseMove.Unit/SmoFacadeListenerTest.cs index 7083043..94892c2 100644 --- a/tests/AgDatabaseMove.Unit/SmoFacadeListenerTest.cs +++ b/tests/AgDatabaseMove.Unit/SmoFacadeListenerTest.cs @@ -5,18 +5,23 @@ namespace AgDatabaseMove.Unit { public class SmoFacadeListenerTest { + + /// + /// Tests for Listener.SplitDomainAndPort() + /// + private const string DOMAIN = "abc.def.ghi"; private const string HOST = "abc"; // {input, expectedDomain, expectedPort (can be null)} public static IEnumerable ValidPorts => new List { - new object[] {$"{DOMAIN}", DOMAIN}, - new object[] {$"{DOMAIN},123", DOMAIN, ",123" }, - new object[] {$"{DOMAIN}\\SQL", DOMAIN, "\\SQL" }, + new object[] { $"{DOMAIN}", DOMAIN }, + new object[] { $"{DOMAIN},123", DOMAIN, ",123" }, + new object[] { $"{DOMAIN}\\SQL", DOMAIN, "\\SQL" }, - new object[] {$"{HOST}", HOST}, - new object[] {$"{HOST},123", HOST, ",123" }, - new object[] {$"{HOST}\\SQL", HOST, "\\SQL" } + new object[] { $"{HOST}", HOST }, + new object[] { $"{HOST},123", HOST, ",123" }, + new object[] { $"{HOST}\\SQL", HOST, "\\SQL" } }; [Theory] @@ -30,11 +35,11 @@ public void ValidPortTests(string input, string expectedDomain, string expectedP // {input, expectedDomain, expectedPort (can be null)} public static IEnumerable InvalidPorts => new List { - new object[] {$"{DOMAIN}:123", $"{DOMAIN}:123" }, - new object[] {$"{DOMAIN}:SQL", $"{DOMAIN}:SQL" }, + new object[] { $"{DOMAIN}:123", $"{DOMAIN}:123" }, + new object[] { $"{DOMAIN}:SQL", $"{DOMAIN}:SQL" }, - new object[] {$"{HOST}:123", $"{HOST}:123" }, - new object[] {$"{HOST}:SQL", $"{HOST}:SQL" }, + new object[] { $"{HOST}:123", $"{HOST}:123" }, + new object[] { $"{HOST}:SQL", $"{HOST}:SQL" }, }; [Theory] @@ -45,5 +50,37 @@ public void InvalidPortTests(string input, string expectedDomain, string expecte Assert.Equal(domain, expectedDomain); Assert.Equal(port, expectedPort); } + + + /// + /// Tests for Listener.GetPreferredPort() + /// + + private const string IPort = ",123"; + private const string INamed = "\\SQL"; + + private const string LPort = ",321"; + private const string LNamed = "\\LQS"; + // { instancePort, listenerPort, preferredPort} + public static IEnumerable PortPreferences => new List { + new object[] { null , null , null}, + new object[] { null , LPort , LPort}, + new object[] { null , LNamed , LNamed}, + new object[] { IPort , null , IPort}, + new object[] { IPort , LPort , IPort}, + new object[] { IPort , LNamed , IPort}, + new object[] { INamed , null , INamed}, + new object[] { INamed , LPort , LPort}, + new object[] { INamed , LNamed , INamed}, + }; + + [Theory] + [MemberData(nameof(PortPreferences))] + public void PortPreferenceTests(string instancePort=null, string listenerPort=null, string preferredPort=null) + { + var port = SmoFacade.Listener.GetPreferredPort(instancePort, listenerPort); + Assert.Equal(port, preferredPort); + } + } } From d48fc10d3afcb107a0508c4ec49d81517ce2e116 Mon Sep 17 00:00:00 2001 From: AdityaNPL Date: Fri, 5 Feb 2021 18:12:20 +0000 Subject: [PATCH 12/14] refactor(linux_dns_issue): renames --- src/SmoFacade/Listener.cs | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/SmoFacade/Listener.cs b/src/SmoFacade/Listener.cs index 3b3098b..40efc0d 100755 --- a/src/SmoFacade/Listener.cs +++ b/src/SmoFacade/Listener.cs @@ -154,15 +154,15 @@ private static Server AgInstanceNameToServer(ref SqlConnectionStringBuilder conn /// The complete domain for the AG listener private static string ResolveDnsHostNameForInstance(string agReplicaInstanceName, string agListenerDomain) { - // Sometimes instances and listeners have ports(or "named instances") + // Sometimes instances and listeners have ports or "named instances" (NI) // Therefore, we strip them off before calling DNS.GetHostEntry() and then add them back to the result - var (listenerDomain, listenerPort) = SplitDomainAndPort(agListenerDomain); - var (instanceName, instancePort) = SplitDomainAndPort(agReplicaInstanceName); - var port = GetPreferredPort(instancePort, listenerPort); + var (listenerDomain, listenerPortOrNi) = SplitDomainAndPort(agListenerDomain); + var (instanceName, instancePortOrNi) = SplitDomainAndPort(agReplicaInstanceName); + var portOrNi = GetPreferredPort(instancePortOrNi, listenerPortOrNi); try { - return $"{Dns.GetHostEntry(instanceName).HostName}{port}"; + return $"{Dns.GetHostEntry(instanceName).HostName}{portOrNi}"; } catch (System.Net.Sockets.SocketException) { @@ -173,24 +173,24 @@ private static string ResolveDnsHostNameForInstance(string agReplicaInstanceName listenerDomainFragments[0] = null; var instanceDomain = $"{instanceName}{string.Join(".", listenerDomainFragments)}"; - return $"{Dns.GetHostEntry(instanceDomain).HostName}{port}"; + return $"{Dns.GetHostEntry(instanceDomain).HostName}{portOrNi}"; } } - // first preference is to add back port over named instances + // first preference is to add back port over named instance (NI) // (port is TCP/IP standard while named instance is only SQL Server standard) - // If both are same, prioritise 'instancePort' over 'listenerPort' (in almost all cases they should be identical) - internal static string GetPreferredPort(string instancePort, string listenerPort) + // If both are same, prioritize 'instancePortOrNi' over 'listenerPortOrNi' (in almost all cases they should be identical) + internal static string GetPreferredPort(string instancePortOrNi, string listenerPortOrNi) { - if (string.IsNullOrEmpty(instancePort) || string.IsNullOrEmpty(listenerPort)) + if (string.IsNullOrEmpty(instancePortOrNi) || string.IsNullOrEmpty(listenerPortOrNi)) { - return (instancePort ?? listenerPort); + return (instancePortOrNi ?? listenerPortOrNi); } - if (instancePort.StartsWith("\\")) + if (instancePortOrNi.StartsWith("\\")) { - return (listenerPort.StartsWith(",")? listenerPort: instancePort); + return (listenerPortOrNi.StartsWith(",") ? listenerPortOrNi : instancePortOrNi); } - return instancePort; + return instancePortOrNi; } // This function handles named instances ("\") in the same way as ports From 98ce65d4830f638f9bc8fd9b91a0c75a1ac324fe Mon Sep 17 00:00:00 2001 From: AdityaNPL Date: Mon, 8 Feb 2021 13:18:13 +0000 Subject: [PATCH 13/14] refactor(port preference): simplified if conditions --- src/SmoFacade/Listener.cs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/SmoFacade/Listener.cs b/src/SmoFacade/Listener.cs index 40efc0d..680f81e 100755 --- a/src/SmoFacade/Listener.cs +++ b/src/SmoFacade/Listener.cs @@ -177,20 +177,16 @@ private static string ResolveDnsHostNameForInstance(string agReplicaInstanceName } } - // first preference is to add back port over named instance (NI) + // First preference is to add back a port over a named instance (NI) // (port is TCP/IP standard while named instance is only SQL Server standard) - // If both are same, prioritize 'instancePortOrNi' over 'listenerPortOrNi' (in almost all cases they should be identical) + // If both are same type, then prioritize instance over listener (in almost all cases they should be identical) internal static string GetPreferredPort(string instancePortOrNi, string listenerPortOrNi) { if (string.IsNullOrEmpty(instancePortOrNi) || string.IsNullOrEmpty(listenerPortOrNi)) { return (instancePortOrNi ?? listenerPortOrNi); } - if (instancePortOrNi.StartsWith("\\")) - { - return (listenerPortOrNi.StartsWith(",") ? listenerPortOrNi : instancePortOrNi); - } - return instancePortOrNi; + return instancePortOrNi.StartsWith("\\") && listenerPortOrNi.StartsWith(",") ? listenerPortOrNi : instancePortOrNi; } // This function handles named instances ("\") in the same way as ports From d2b56c68a8f548cd07eae99be9bdffa4ff90633a Mon Sep 17 00:00:00 2001 From: AdityaNPL Date: Tue, 9 Feb 2021 13:56:57 +0000 Subject: [PATCH 14/14] refactor(Listener.cs): PR suggestions for renames and test data --- src/SmoFacade/Listener.cs | 24 +++---- .../SmoFacadeListenerTest.cs | 62 +++++++++---------- 2 files changed, 43 insertions(+), 43 deletions(-) diff --git a/src/SmoFacade/Listener.cs b/src/SmoFacade/Listener.cs index 680f81e..4eaf9dc 100755 --- a/src/SmoFacade/Listener.cs +++ b/src/SmoFacade/Listener.cs @@ -154,15 +154,15 @@ private static Server AgInstanceNameToServer(ref SqlConnectionStringBuilder conn /// The complete domain for the AG listener private static string ResolveDnsHostNameForInstance(string agReplicaInstanceName, string agListenerDomain) { - // Sometimes instances and listeners have ports or "named instances" (NI) + // Sometimes instances and listeners have ports or named instances // Therefore, we strip them off before calling DNS.GetHostEntry() and then add them back to the result - var (listenerDomain, listenerPortOrNi) = SplitDomainAndPort(agListenerDomain); - var (instanceName, instancePortOrNi) = SplitDomainAndPort(agReplicaInstanceName); - var portOrNi = GetPreferredPort(instancePortOrNi, listenerPortOrNi); + var (listenerDomain, listenerPortOrNamedInstance) = SplitDomainAndPort(agListenerDomain); + var (instanceName, instancePortOrNamedInstance) = SplitDomainAndPort(agReplicaInstanceName); + var preferredPortOrNamedInstance = GetPreferredPort(instancePortOrNamedInstance, listenerPortOrNamedInstance); try { - return $"{Dns.GetHostEntry(instanceName).HostName}{portOrNi}"; + return $"{Dns.GetHostEntry(instanceName).HostName}{preferredPortOrNamedInstance}"; } catch (System.Net.Sockets.SocketException) { @@ -173,20 +173,22 @@ private static string ResolveDnsHostNameForInstance(string agReplicaInstanceName listenerDomainFragments[0] = null; var instanceDomain = $"{instanceName}{string.Join(".", listenerDomainFragments)}"; - return $"{Dns.GetHostEntry(instanceDomain).HostName}{portOrNi}"; + return $"{Dns.GetHostEntry(instanceDomain).HostName}{preferredPortOrNamedInstance}"; } } - // First preference is to add back a port over a named instance (NI) + // First preference is to add back a port over a named instance // (port is TCP/IP standard while named instance is only SQL Server standard) // If both are same type, then prioritize instance over listener (in almost all cases they should be identical) - internal static string GetPreferredPort(string instancePortOrNi, string listenerPortOrNi) + internal static string GetPreferredPort(string instancePortOrNamedInstance, string listenerPortOrNamedInstance) { - if (string.IsNullOrEmpty(instancePortOrNi) || string.IsNullOrEmpty(listenerPortOrNi)) + if (string.IsNullOrEmpty(instancePortOrNamedInstance) || string.IsNullOrEmpty(listenerPortOrNamedInstance)) { - return (instancePortOrNi ?? listenerPortOrNi); + return (instancePortOrNamedInstance ?? listenerPortOrNamedInstance); } - return instancePortOrNi.StartsWith("\\") && listenerPortOrNi.StartsWith(",") ? listenerPortOrNi : instancePortOrNi; + return instancePortOrNamedInstance.StartsWith("\\") && listenerPortOrNamedInstance.StartsWith(",") + ? listenerPortOrNamedInstance + : instancePortOrNamedInstance; } // This function handles named instances ("\") in the same way as ports diff --git a/tests/AgDatabaseMove.Unit/SmoFacadeListenerTest.cs b/tests/AgDatabaseMove.Unit/SmoFacadeListenerTest.cs index 94892c2..7f3c27d 100644 --- a/tests/AgDatabaseMove.Unit/SmoFacadeListenerTest.cs +++ b/tests/AgDatabaseMove.Unit/SmoFacadeListenerTest.cs @@ -12,38 +12,40 @@ public class SmoFacadeListenerTest private const string DOMAIN = "abc.def.ghi"; private const string HOST = "abc"; + private const string PORT = ",123"; + private const string NAMED_INSTANCE = "\\SQL"; + private const string BAD_PORT = ":123"; + private const string BAD_NAMED_INSTANCE = "SQL"; // {input, expectedDomain, expectedPort (can be null)} public static IEnumerable ValidPorts => new List { - new object[] { $"{DOMAIN}", DOMAIN }, - new object[] { $"{DOMAIN},123", DOMAIN, ",123" }, - new object[] { $"{DOMAIN}\\SQL", DOMAIN, "\\SQL" }, + new object[] { DOMAIN, DOMAIN, null }, + new object[] { $"{DOMAIN}{PORT}", DOMAIN, PORT }, + new object[] { $"{DOMAIN}{NAMED_INSTANCE}", DOMAIN, NAMED_INSTANCE }, - new object[] { $"{HOST}", HOST }, - new object[] { $"{HOST},123", HOST, ",123" }, - new object[] { $"{HOST}\\SQL", HOST, "\\SQL" } + new object[] { HOST, HOST, null }, + new object[] { $"{HOST}{PORT}", HOST, PORT }, + new object[] { $"{HOST}{NAMED_INSTANCE}", HOST, NAMED_INSTANCE } }; - [Theory] - [MemberData(nameof(ValidPorts))] + [Theory, MemberData(nameof(ValidPorts))] public void ValidPortTests(string input, string expectedDomain, string expectedPort=null) { var (domain, port) = SmoFacade.Listener.SplitDomainAndPort(input); Assert.Equal(domain, expectedDomain); Assert.Equal(port, expectedPort); } - + // {input, expectedDomain, expectedPort (can be null)} public static IEnumerable InvalidPorts => new List { - new object[] { $"{DOMAIN}:123", $"{DOMAIN}:123" }, - new object[] { $"{DOMAIN}:SQL", $"{DOMAIN}:SQL" }, + new object[] { $"{DOMAIN}{BAD_PORT}", $"{DOMAIN}{BAD_PORT}", null }, + new object[] { $"{DOMAIN}{BAD_NAMED_INSTANCE}", $"{DOMAIN}{BAD_NAMED_INSTANCE}", null }, - new object[] { $"{HOST}:123", $"{HOST}:123" }, - new object[] { $"{HOST}:SQL", $"{HOST}:SQL" }, + new object[] { $"{HOST}{BAD_PORT}", $"{HOST}{BAD_PORT}", null }, + new object[] { $"{HOST}{BAD_NAMED_INSTANCE}", $"{HOST}{BAD_NAMED_INSTANCE}", null }, }; - [Theory] - [MemberData(nameof(InvalidPorts))] + [Theory, MemberData(nameof(InvalidPorts))] public void InvalidPortTests(string input, string expectedDomain, string expectedPort=null) { var (domain, port) = SmoFacade.Listener.SplitDomainAndPort(input); @@ -61,25 +63,21 @@ public void InvalidPortTests(string input, string expectedDomain, string expecte private const string LPort = ",321"; private const string LNamed = "\\LQS"; - // { instancePort, listenerPort, preferredPort} - public static IEnumerable PortPreferences => new List { - new object[] { null , null , null}, - new object[] { null , LPort , LPort}, - new object[] { null , LNamed , LNamed}, - new object[] { IPort , null , IPort}, - new object[] { IPort , LPort , IPort}, - new object[] { IPort , LNamed , IPort}, - new object[] { INamed , null , INamed}, - new object[] { INamed , LPort , LPort}, - new object[] { INamed , LNamed , INamed}, - }; - + [Theory] - [MemberData(nameof(PortPreferences))] - public void PortPreferenceTests(string instancePort=null, string listenerPort=null, string preferredPort=null) + [InlineData(null, null, null)] + [InlineData(null, LPort, LPort)] + [InlineData(null, LNamed, LNamed)] + [InlineData(IPort, null, IPort)] + [InlineData(IPort, LPort, IPort)] + [InlineData(IPort, LNamed, IPort)] + [InlineData(INamed, null, INamed)] + [InlineData(INamed, LPort, LPort)] + [InlineData(INamed, LNamed, INamed)] + public void PortPreferenceTests(string instancePortOrNamedInstance, string listenerPortOrNamedInstance, string expectedResult) { - var port = SmoFacade.Listener.GetPreferredPort(instancePort, listenerPort); - Assert.Equal(port, preferredPort); + var preferredPortOrNamedInstance = SmoFacade.Listener.GetPreferredPort(instancePortOrNamedInstance, listenerPortOrNamedInstance); + Assert.Equal(preferredPortOrNamedInstance, expectedResult); } }