From 06eeb3883393817909781e99787832034b488fb0 Mon Sep 17 00:00:00 2001 From: Timothy Mothra Date: Tue, 31 Oct 2023 17:09:00 -0700 Subject: [PATCH] [AzureMonitorExporter] add support for LiveMetrics to the ConnectionString Parser (#38373) * add support for LiveMetrics to the ConnectionString Parser * refactor GetEndpoint method --- .../ConnectionStringParser.cs | 17 ++++-- .../ConnectionString/ConnectionVars.cs | 5 +- .../Internals/ConnectionString/Constants.cs | 15 ++++++ .../ConnectionStringParserTests.cs | 53 ++++++++++--------- 4 files changed, 58 insertions(+), 32 deletions(-) diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/ConnectionString/ConnectionStringParser.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/ConnectionString/ConnectionStringParser.cs index 9075238955ccd..a8366a4ad7141 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/ConnectionString/ConnectionStringParser.cs +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/ConnectionString/ConnectionStringParser.cs @@ -41,6 +41,7 @@ public static ConnectionVars GetValues(string connectionString) return new ConnectionVars( instrumentationKey: connString.GetInstrumentationKey(), ingestionEndpoint: connString.GetIngestionEndpoint(), + liveEndpoint: connString.GetLiveEndpoint(), aadAudience: connString.GetAADAudience()); } catch (Exception ex) @@ -54,6 +55,12 @@ public static ConnectionVars GetValues(string connectionString) internal static string GetInstrumentationKey(this AzureCoreConnectionString connectionString) => connectionString.GetRequired(Constants.InstrumentationKeyKey); + internal static string GetIngestionEndpoint(this AzureCoreConnectionString connectionString) => + connectionString.GetEndpoint(endpointKeyName: Constants.IngestionExplicitEndpointKey, prefix: Constants.IngestionPrefix, defaultValue: Constants.DefaultIngestionEndpoint); + + internal static string GetLiveEndpoint(this AzureCoreConnectionString connectionString) => + connectionString.GetEndpoint(endpointKeyName: Constants.LiveExplicitEndpointKey, prefix: Constants.LivePrefix, defaultValue: Constants.DefaultLiveEndpoint); + /// /// Evaluate connection string and return the requested endpoint. /// @@ -64,29 +71,29 @@ public static ConnectionVars GetValues(string connectionString) /// 3. use default endpoint (location is ignored) /// This behavior is required by the Connection String Specification. /// - internal static string GetIngestionEndpoint(this AzureCoreConnectionString connectionString) + internal static string GetEndpoint(this AzureCoreConnectionString connectionString, string endpointKeyName, string prefix, string defaultValue) { // Passing the user input values through the Uri constructor will verify that we've built a valid endpoint. Uri? uri; - if (connectionString.TryGetNonRequiredValue(Constants.IngestionExplicitEndpointKey, out string? explicitEndpoint)) + if (connectionString.TryGetNonRequiredValue(endpointKeyName, out string? explicitEndpoint)) { if (!Uri.TryCreate(explicitEndpoint, UriKind.Absolute, out uri)) { - throw new ArgumentException($"The value for {Constants.IngestionExplicitEndpointKey} is invalid. '{explicitEndpoint}'"); + throw new ArgumentException($"The value for {endpointKeyName} is invalid. '{explicitEndpoint}'"); } } else if (connectionString.TryGetNonRequiredValue(Constants.EndpointSuffixKey, out string? endpointSuffix)) { var location = connectionString.GetNonRequired(Constants.LocationKey); - if (!TryBuildUri(prefix: Constants.IngestionPrefix, suffix: endpointSuffix, location: location, uri: out uri)) + if (!TryBuildUri(prefix: prefix, suffix: endpointSuffix, location: location, uri: out uri)) { throw new ArgumentException($"The value for {Constants.EndpointSuffixKey} is invalid. '{endpointSuffix}'"); } } else { - return Constants.DefaultIngestionEndpoint; + return defaultValue; } return uri.AbsoluteUri; diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/ConnectionString/ConnectionVars.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/ConnectionString/ConnectionVars.cs index 5f063ee42b78f..e92bd5b629040 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/ConnectionString/ConnectionVars.cs +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/ConnectionString/ConnectionVars.cs @@ -8,10 +8,11 @@ namespace Azure.Monitor.OpenTelemetry.Exporter.Internals.ConnectionString /// internal class ConnectionVars { - public ConnectionVars(string instrumentationKey, string ingestionEndpoint, string? aadAudience) + public ConnectionVars(string instrumentationKey, string ingestionEndpoint, string liveEndpoint, string? aadAudience) { this.InstrumentationKey = instrumentationKey; this.IngestionEndpoint = ingestionEndpoint; + this.LiveEndpoint = liveEndpoint; this.AadAudience = aadAudience; } @@ -19,6 +20,8 @@ public ConnectionVars(string instrumentationKey, string ingestionEndpoint, strin public string IngestionEndpoint { get; } + public string LiveEndpoint { get; } + public string? AadAudience { get; } } } diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/ConnectionString/Constants.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/ConnectionString/Constants.cs index 3c558cbb203d1..d2dfe33997b11 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/ConnectionString/Constants.cs +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/ConnectionString/Constants.cs @@ -10,16 +10,31 @@ internal static class Constants /// internal const string DefaultIngestionEndpoint = "https://dc.services.visualstudio.com/"; + /// + /// Default endpoint for Live Metrics (aka QuickPulse). + /// + internal const string DefaultLiveEndpoint = "https://rt.services.visualstudio.com/"; + /// /// Sub-domain for Ingestion endpoint (aka Breeze). (https://dc.applicationinsights.azure.com/). /// internal const string IngestionPrefix = "dc"; + /// + /// Sub-domain for Live Metrics endpoint (aka QuickPulse). (https://live.applicationinsights.azure.com/). + /// + internal const string LivePrefix = "live"; + /// /// This is the key that a customer would use to specify an explicit endpoint in the connection string. /// internal const string IngestionExplicitEndpointKey = "IngestionEndpoint"; + /// + /// This is the key that a customer would use to specify an explicit endpoint in the connection string. + /// + internal const string LiveExplicitEndpointKey = "LiveEndpoint"; + /// /// This is the key that a customer would use to specify an instrumentation key in the connection string. /// diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/ConnectionString/ConnectionStringParserTests.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/ConnectionString/ConnectionStringParserTests.cs index 8400b839ee669..1ca1fc62fdc9e 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/ConnectionString/ConnectionStringParserTests.cs +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/ConnectionString/ConnectionStringParserTests.cs @@ -15,8 +15,9 @@ public class ConnectionStringParserTests public void TestConnectionString_Full() { RunTest( - connectionString: "InstrumentationKey=00000000-0000-0000-0000-000000000000;IngestionEndpoint=https://ingestion.azuremonitor.com/;AADAudience=https://monitor.azure.com//testing", + connectionString: "InstrumentationKey=00000000-0000-0000-0000-000000000000;IngestionEndpoint=https://ingestion.azuremonitor.com/;LiveEndpoint=https://live.azuremonitor.com/;AADAudience=https://monitor.azure.com//testing", expectedIngestionEndpoint: "https://ingestion.azuremonitor.com/", + expectedLiveEndpoint: "https://live.azuremonitor.com/", expectedInstrumentationKey: "00000000-0000-0000-0000-000000000000", expectedAadAudience: "https://monitor.azure.com//testing"); } @@ -27,6 +28,7 @@ public void TestInstrumentationKey_IsRequired() Assert.Throws(() => RunTest( connectionString: "EndpointSuffix=ingestion.azuremonitor.com", expectedIngestionEndpoint: null, + expectedLiveEndpoint: null, expectedInstrumentationKey: null)); } @@ -36,6 +38,7 @@ public void TestInstrumentationKey_CannotBeEmpty() Assert.Throws(() => RunTest( connectionString: "InstrumentationKey=;EndpointSuffix=ingestion.azuremonitor.com", expectedIngestionEndpoint: null, + expectedLiveEndpoint: null, expectedInstrumentationKey: null)); } @@ -45,6 +48,7 @@ public void TestDefaultEndpoints() RunTest( connectionString: "InstrumentationKey=00000000-0000-0000-0000-000000000000", expectedIngestionEndpoint: Constants.DefaultIngestionEndpoint, + expectedLiveEndpoint: Constants.DefaultLiveEndpoint, expectedInstrumentationKey: "00000000-0000-0000-0000-000000000000"); } @@ -54,6 +58,7 @@ public void TestEndpointSuffix() RunTest( connectionString: "InstrumentationKey=00000000-0000-0000-0000-000000000000;EndpointSuffix=ingestion.azuremonitor.com", expectedIngestionEndpoint: "https://dc.ingestion.azuremonitor.com/", + expectedLiveEndpoint: "https://live.ingestion.azuremonitor.com/", expectedInstrumentationKey: "00000000-0000-0000-0000-000000000000"); } @@ -61,8 +66,9 @@ public void TestEndpointSuffix() public void TestEndpointSuffix_WithExplicitOverride() { RunTest( - connectionString: "InstrumentationKey=00000000-0000-0000-0000-000000000000;EndpointSuffix=ingestion.azuremonitor.com;IngestionEndpoint=https://custom.contoso.com:444/", + connectionString: "InstrumentationKey=00000000-0000-0000-0000-000000000000;EndpointSuffix=ingestion.azuremonitor.com;IngestionEndpoint=https://custom.contoso.com:444/;LiveEndpoint=https://custom.contoso.com:555", expectedIngestionEndpoint: "https://custom.contoso.com:444/", + expectedLiveEndpoint: "https://custom.contoso.com:555/", expectedInstrumentationKey: "00000000-0000-0000-0000-000000000000"); } @@ -72,6 +78,7 @@ public void TestEndpointSuffix_WithLocation() RunTest( connectionString: "InstrumentationKey=00000000-0000-0000-0000-000000000000;EndpointSuffix=ingestion.azuremonitor.com;Location=westus2", expectedIngestionEndpoint: "https://westus2.dc.ingestion.azuremonitor.com/", + expectedLiveEndpoint: "https://westus2.live.ingestion.azuremonitor.com/", expectedInstrumentationKey: "00000000-0000-0000-0000-000000000000"); } @@ -79,17 +86,20 @@ public void TestEndpointSuffix_WithLocation() public void TestEndpointSuffix_WithLocation_WithExplicitOverride() { RunTest( - connectionString: "InstrumentationKey=00000000-0000-0000-0000-000000000000;EndpointSuffix=ingestion.azuremonitor.com;Location=westus2;IngestionEndpoint=https://custom.contoso.com:444/", + connectionString: "InstrumentationKey=00000000-0000-0000-0000-000000000000;EndpointSuffix=ingestion.azuremonitor.com;Location=westus2;IngestionEndpoint=https://custom.contoso.com:444/;LiveEndpoint=https://custom.contoso.com:555", expectedIngestionEndpoint: "https://custom.contoso.com:444/", + expectedLiveEndpoint: "https://custom.contoso.com:555/", expectedInstrumentationKey: "00000000-0000-0000-0000-000000000000"); } [Fact] public void TestExplicitOverride_PreservesSchema() { + // if "http" has been specified, we should not change it to "https". RunTest( - connectionString: "InstrumentationKey=00000000-0000-0000-0000-000000000000;IngestionEndpoint=http://custom.contoso.com:444/", + connectionString: "InstrumentationKey=00000000-0000-0000-0000-000000000000;IngestionEndpoint=http://custom.contoso.com:444/;LiveEndpoint=http://custom.contoso.com:555", expectedIngestionEndpoint: "http://custom.contoso.com:444/", + expectedLiveEndpoint: "http://custom.contoso.com:555/", expectedInstrumentationKey: "00000000-0000-0000-0000-000000000000"); } @@ -97,70 +107,61 @@ public void TestExplicitOverride_PreservesSchema() public void TestExplicitOverride_InvalidValue() { Assert.Throws(() => RunTest( - connectionString: "InstrumentationKey=00000000-0000-0000-0000-000000000000;IngestionEndpoint=https:////custom.contoso.com", - expectedIngestionEndpoint: null, - expectedInstrumentationKey: null)); + connectionString: "InstrumentationKey=00000000-0000-0000-0000-000000000000;IngestionEndpoint=https:////custom.contoso.com")); } [Fact] public void TestExplicitOverride_InvalidValue2() { Assert.Throws(() => RunTest( - connectionString: "InstrumentationKey=00000000-0000-0000-0000-000000000000;IngestionEndpoint=https://www.~!@#$%&^*()_{}{}>:L\":\"_+_+_", - expectedIngestionEndpoint: null, - expectedInstrumentationKey: null)); + connectionString: "InstrumentationKey=00000000-0000-0000-0000-000000000000;IngestionEndpoint=https://www.~!@#$%&^*()_{}{}>:L\":\"_+_+_")); } [Fact] public void TestExplicitOverride_InvalidValue3() { Assert.Throws(() => RunTest( - connectionString: "InstrumentationKey=00000000-0000-0000-0000-000000000000;EndpointSuffix=~!@#$%&^*()_{}{}>:L\":\"_+_+_", - expectedIngestionEndpoint: null, - expectedInstrumentationKey: null)); + connectionString: "InstrumentationKey=00000000-0000-0000-0000-000000000000;EndpointSuffix=~!@#$%&^*()_{}{}>:L\":\"_+_+_")); } [Fact] public void TestExplicitOverride_InvalidLocation() { Assert.Throws(() => RunTest( - connectionString: "InstrumentationKey=00000000-0000-0000-0000-000000000000;EndpointSuffix=ingestion.azuremonitor.com;Location=~!@#$%&^*()_{}{}>:L\":\"_+_+_", - expectedIngestionEndpoint: null, - expectedInstrumentationKey: null)); + connectionString: "InstrumentationKey=00000000-0000-0000-0000-000000000000;EndpointSuffix=ingestion.azuremonitor.com;Location=~!@#$%&^*()_{}{}>:L\":\"_+_+_")); } [Fact] public void TestMaliciousConnectionString() { Assert.Throws(() => RunTest( - connectionString: new string('*', Constants.ConnectionStringMaxLength + 1), - expectedIngestionEndpoint: null, - expectedInstrumentationKey: null)); + connectionString: new string('*', Constants.ConnectionStringMaxLength + 1))); } [Fact] public void TestParseConnectionString_Empty() { Assert.Throws(() => RunTest( - connectionString: "", - expectedIngestionEndpoint: null, - expectedInstrumentationKey: null)); + connectionString: "")); } [Fact] public void TestEndpointProvider_NoInstrumentationKey() { Assert.Throws(() => RunTest( - connectionString: "key1=value1;key2=value2;key3=value3", - expectedIngestionEndpoint: null, - expectedInstrumentationKey: null)); + connectionString: "key1=value1;key2=value2;key3=value3")); } - private void RunTest(string connectionString, string? expectedIngestionEndpoint, string? expectedInstrumentationKey, string? expectedAadAudience = null) + private void RunTest(string connectionString, + string? expectedIngestionEndpoint = null, + string? expectedLiveEndpoint = null, + string? expectedInstrumentationKey = null, + string? expectedAadAudience = null) { var connectionVars = ConnectionStringParser.GetValues(connectionString); Assert.Equal(expectedIngestionEndpoint, connectionVars.IngestionEndpoint); + Assert.Equal(expectedLiveEndpoint, connectionVars.LiveEndpoint); Assert.Equal(expectedInstrumentationKey, connectionVars.InstrumentationKey); Assert.Equal(expectedAadAudience, connectionVars.AadAudience); }