public void FabricServiceEndpointSelector_SelectsEmptyListenerEndpoint_EmptyListenerName() { var serviceName = new Uri("fabric:/Application/Service"); var emptyStringMatchesAnyListener = false; var listenerName = string.Empty; var allowedScheme = "https"; var endpointAddress = "https://*****:*****@"{{ 'Endpoints': {{ 'DifferentServiceEndpoint1': 'https://localhost:123/query', 'DifferentServiceEndpoint2': 'https://loopback:123/query', '{listenerName}': '{endpointAddress}', 'DifferentServiceEndpoint3': 'https://localhost:456/query', 'DifferentServiceEndpoint4': 'https://loopback:456/query' }} }}".Replace("'", "\""); var fabricServiceEndpoint = new FabricServiceEndpoint( listenerNames: new[] { listenerName }, allowedSchemePredicate: (scheme) => scheme == allowedScheme, emptyStringMatchesAnyListener: emptyStringMatchesAnyListener); ServiceEndpointCollection.TryParseEndpointsString(endpoints, out var serviceEndpointCollection); FabricServiceEndpointSelector.TryGetEndpoint(fabricServiceEndpoint, serviceEndpointCollection, out var endpointUri) .Should().BeTrue("There should be a matching endpoint"); endpointUri.ToString().Should().BeEquivalentTo(endpointAddress); }
public void FabricServiceEndpointSelector_ReturnsFalseOnMalformedUri_EmptyListenerName() { var serviceName = new Uri("fabric:/Application/Service"); var emptyStringMatchesAnyListener = true; var listenerName = string.Empty; var allowedScheme = "https"; var endpointAddress = "/alsoMalformed"; var endpoints = $@"{{ 'Endpoints': {{ 'DifferentServiceEndpoint1': '/malformed', 'DifferentServiceEndpoint2': '/malformed', '{listenerName}': '{endpointAddress}', 'DifferentServiceEndpoint3': '/malformed', 'DifferentServiceEndpoint4': '/malformed' }} }}".Replace("'", "\""); var fabricServiceEndpoint = new FabricServiceEndpoint( listenerNames: new[] { listenerName }, allowedSchemePredicate: (scheme) => scheme == allowedScheme, emptyStringMatchesAnyListener: emptyStringMatchesAnyListener); ServiceEndpointCollection.TryParseEndpointsString(endpoints, out var serviceEndpointCollection); FabricServiceEndpointSelector.TryGetEndpoint(fabricServiceEndpoint, serviceEndpointCollection, out var endpointUri) .Should().BeFalse("There should be no matching endpoint"); endpointUri.Should().BeNull(); }
public void FabricServiceEndpointSelector_SelectsEndpointInOrdinalStringOrder_EmptyListenerName() { var serviceName = new Uri("fabric:/Application/Service"); var emptyStringMatchesAnyListener = true; var allowedScheme = "https"; var endpoints = $@"{{ 'Endpoints': {{ 'SelectedServiceEndpoint': 'https://localhost:123/selected', 'notSelected1': 'https://loopback:123/query', 'notSelected2': 'https://localhost:456/query', 'notSelected3': 'https://loopback:456/query' }} }}".Replace("'", "\""); var fabricServiceEndpoint = new FabricServiceEndpoint( listenerNames: new[] { string.Empty }, allowedSchemePredicate: (scheme) => scheme == allowedScheme, emptyStringMatchesAnyListener: emptyStringMatchesAnyListener); ServiceEndpointCollection.TryParseEndpointsString(endpoints, out var serviceEndpointCollection); FabricServiceEndpointSelector.TryGetEndpoint(fabricServiceEndpoint, serviceEndpointCollection, out var endpointUri) .Should().BeTrue("There should be a matching endpoint"); endpointUri.ToString().Should().BeEquivalentTo("https://localhost:123/selected"); }
public void FabricServiceEndpointSelector_NoValidEndpointBasedOnScheme_NamedListener() { var serviceName = new Uri("fabric:/Application/Service"); var emptyStringMatchesAnyListener = true; var listenerName = "ServiceEndpointSecure"; var allowedScheme = "https"; var endpoints = $@"{{ 'Endpoints': {{ 'DifferentServiceEndpoint1': '/malformed', '{listenerName}': 'http://localhost/invalidScheme', }} }}".Replace("'", "\""); var fabricServiceEndpoint = new FabricServiceEndpoint( listenerNames: new[] { listenerName }, allowedSchemePredicate: (scheme) => scheme == allowedScheme, emptyStringMatchesAnyListener: emptyStringMatchesAnyListener); ServiceEndpointCollection.TryParseEndpointsString(endpoints, out var serviceEndpointCollection); FabricServiceEndpointSelector.TryGetEndpoint(fabricServiceEndpoint, serviceEndpointCollection, out var endpointUri) .Should().BeFalse("There should be no matching endpoint because of scheme mismatch."); endpointUri.Should().BeNull(); }
public void FabricServiceEndpointSelector_SelectsEndpointBasedOnScheme_MultipleRequestedListeners() { var serviceName = new Uri("fabric:/Application/Service"); var emptyStringMatchesAnyListener = true; var listenerNames = new[] { "ServiceEndpointSecure", string.Empty }; var allowedScheme = "https"; var endpointAddress = "https://*****:*****@"{{ 'Endpoints': {{ 'DifferentServiceEndpoint1': '/malformed', 'ServiceEndpointSecure': 'http://localhost/invalidScheme', 'ValidServiceEndpoint': '{endpointAddress}' }} }}".Replace("'", "\""); var fabricServiceEndpoint = new FabricServiceEndpoint( listenerNames: listenerNames, allowedSchemePredicate: (scheme) => scheme == allowedScheme, emptyStringMatchesAnyListener: emptyStringMatchesAnyListener); ServiceEndpointCollection.TryParseEndpointsString(endpoints, out var serviceEndpointCollection); FabricServiceEndpointSelector.TryGetEndpoint(fabricServiceEndpoint, serviceEndpointCollection, out var endpointUri) .Should().BeTrue("There should be a matching endpoint"); endpointUri.ToString().Should().BeEquivalentTo(endpointAddress); }
public void FabricServiceEndpointSelector_NoMatchingEndpointScheme() { var serviceName = new Uri("fabric:/Application/Service"); var emptyStringMatchesAnyListener = true; var listenerName = "ServiceEndpoint"; var allowedScheme = "http"; var endpointAddress = "https://*****:*****@"{{ 'Endpoints': {{ 'DifferentServiceEndpoint1': 'https://localhost:123/query', 'DifferentServiceEndpoint2': 'https://loopback:123/query', '{listenerName}': '{endpointAddress}', 'DifferentServiceEndpoint3': 'https://localhost:456/query', 'DifferentServiceEndpoint4': 'https://loopback:456/query' }} }}".Replace("'", "\""); var fabricServiceEndpoint = new FabricServiceEndpoint( listenerNames: new[] { listenerName }, allowedSchemePredicate: (scheme) => scheme == allowedScheme, emptyStringMatchesAnyListener: emptyStringMatchesAnyListener); ServiceEndpointCollection.TryParseEndpointsString(endpoints, out var serviceEndpointCollection); FabricServiceEndpointSelector.TryGetEndpoint(fabricServiceEndpoint, serviceEndpointCollection, out var endpointUri) .Should().BeFalse("No matching endpoint with specified scheme."); endpointUri.Should().BeNull(); }
public void FabricServiceEndpointSelector_NoExceptionOnMalformedUri_EmptyListener() { // Arrange var serviceName = new Uri("fabric:/Application/Service"); var emptyStringMatchesAnyListener = true; var listenerName = string.Empty; var allowedScheme = "https"; var endpointAddress = "https://*****:*****@"{{ 'Endpoints': {{ 'DifferentServiceEndpoint1': '/malformed', 'DifferentServiceEndpoint2': '/malformed', 'ValidServiceEndpoint': '{endpointAddress}', 'DifferentServiceEndpoint3': '/malformed', 'DifferentServiceEndpoint4': '/malformed' }} }}".Replace("'", "\""); var fabricServiceEndpoint = new FabricServiceEndpoint( listenerNames: new[] { listenerName }, allowedSchemePredicate: (scheme) => scheme == allowedScheme, emptyStringMatchesAnyListener: emptyStringMatchesAnyListener); ServiceEndpointCollection.TryParseEndpointsString(endpoints, out var serviceEndpointCollection); // Act + Assert FabricServiceEndpointSelector.TryGetEndpoint(fabricServiceEndpoint, serviceEndpointCollection, out var endpointUri) .Should().BeTrue("There should be a matching endpoint"); endpointUri.ToString().Should().BeEquivalentTo(endpointAddress); }
private DestinationConfig BuildDestination(ReplicaWrapper replica, string listenerName, string healthListenerName, PartitionWrapper partition) { if (!ServiceEndpointCollection.TryParseEndpointsString(replica.ReplicaAddress, out var serviceEndpointCollection)) { throw new ConfigException($"Could not parse endpoints for replica {replica.Id}."); } // TODO: FabricServiceEndpoint has some other fields we are ignoring here. Decide which ones are relevant and fix this call. var serviceEndpoint = new FabricServiceEndpoint( listenerNames: new[] { listenerName }, allowedSchemePredicate: HttpsSchemeSelector, emptyStringMatchesAnyListener: true); if (!FabricServiceEndpointSelector.TryGetEndpoint(serviceEndpoint, serviceEndpointCollection, out var endpointUri)) { throw new ConfigException($"No acceptable endpoints found for replica '{replica.Id}'. Search criteria: listenerName='{listenerName}', emptyStringMatchesAnyListener=true."); } // Get service endpoint from the health listener, health listener is optional. Uri healthEndpointUri = null; if (!string.IsNullOrEmpty(healthListenerName)) { var healthEndpoint = new FabricServiceEndpoint( listenerNames: new[] { healthListenerName }, allowedSchemePredicate: HttpsSchemeSelector, emptyStringMatchesAnyListener: true); if (!FabricServiceEndpointSelector.TryGetEndpoint(healthEndpoint, serviceEndpointCollection, out healthEndpointUri)) { throw new ConfigException($"No acceptable health endpoints found for replica '{replica.Id}'. Search criteria: listenerName='{healthListenerName}', emptyStringMatchesAnyListener=true."); } } return new DestinationConfig { Address = endpointUri.AbsoluteUri, Health = healthEndpointUri?.AbsoluteUri, Metadata = new Dictionary<string, string> { { "PartitionId", partition.Id.ToString() ?? string.Empty }, { "NamedPartitionName", partition.Name ?? string.Empty }, { "ReplicaId", replica.Id.ToString() ?? string.Empty } } }; }
/// <summary> /// Selects and endpoint (aka "listener") from a presented <paramref name="endpoints"/> collection /// that satisfies all constraints of <paramref name="fabricServiceEndpoint"/>. /// </summary> /// <param name="fabricServiceEndpoint">User-defined info and constraints for the selecting an endpoint from <paramref name="fabricServiceEndpoint"/>.</param> /// <param name="endpoints">Collection of endpoints to choose from.</param> /// <param name="endpointUri">The endpoint URI to extract.</param> /// <returns>Boolean indicating whether an endpoint URI was successfully retrieved.</returns> public static bool TryGetEndpoint( FabricServiceEndpoint fabricServiceEndpoint, ServiceEndpointCollection endpoints, out Uri endpointUri) { _ = fabricServiceEndpoint ?? throw new ArgumentNullException(nameof(fabricServiceEndpoint)); _ = endpoints ?? throw new ArgumentNullException(nameof(endpoints)); endpointUri = null; string endpointAddress = null; foreach (var listenerName in fabricServiceEndpoint.ListenerNames) { // SF Reverse Proxy endpoint selection logic: https://github.com/microsoft/service-fabric/blob/1e118f02294c99b61e676c07ac97283ee12197d4/src/prod/src/Management/ApplicationGateway/Http/ServiceEndpointsList.cpp#L52 if (listenerName == string.Empty && fabricServiceEndpoint.EmptyStringMatchesAnyListener) { endpointUri = endpoints.ToReadOnlyDictionary() // NOTE: Ordinal comparison used to match sort order of Service Fabric Reverse Proxy .OrderBy(listenerAddressPair => listenerAddressPair.Key, StringComparer.Ordinal) // From the endpoints above, select endpoints with valid URIs .Select(listenerAddressPair => { if (Uri.TryCreate(listenerAddressPair.Value, UriKind.Absolute, out var uri)) { return(uri); } return(null); }) .Where(replicaAddress => replicaAddress != null) // Pick first endpoint that matches scheme predicate. .FirstOrDefault(replicaAddress => { if (fabricServiceEndpoint.AllowedSchemePredicate(replicaAddress.Scheme)) { return(true); } return(false); }); // Bail as soon as first valid endpoint is found. if (endpointUri != null) { // CoreFrameworkFabricTrace.Instance.TraceVerbose("Located endpoint URI is '{0}'", endpointUri); return(true); } } else { // Pick named listener endpoint if (endpoints.TryGetEndpointAddress(listenerName: listenerName, endpointAddress: out endpointAddress)) { if (!Uri.TryCreate(endpointAddress, UriKind.Absolute, out var endpointUri_)) { continue; } // Match the Uri against allowed scheme predicate. if (!fabricServiceEndpoint.AllowedSchemePredicate(endpointUri_.Scheme)) { continue; } endpointUri = endpointUri_; // CoreFrameworkFabricTrace.Instance.TraceVerbose("Located endpoint URI is '{0}'", endpointUri); return(true); } } } return(false); }