Skip to content

Commit

Permalink
[AzureMonitorExporter] add support for LiveMetrics to the ConnectionS…
Browse files Browse the repository at this point in the history
…tring Parser (#38373)

* add support for LiveMetrics to the ConnectionString Parser

* refactor GetEndpoint method
  • Loading branch information
TimothyMothra authored Nov 1, 2023
1 parent 96b537f commit 06eeb38
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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);

/// <summary>
/// Evaluate connection string and return the requested endpoint.
/// </summary>
Expand All @@ -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.
/// </remarks>
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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,20 @@ namespace Azure.Monitor.OpenTelemetry.Exporter.Internals.ConnectionString
/// </summary>
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;
}

public string InstrumentationKey { get; }

public string IngestionEndpoint { get; }

public string LiveEndpoint { get; }

public string? AadAudience { get; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,31 @@ internal static class Constants
/// </summary>
internal const string DefaultIngestionEndpoint = "https://dc.services.visualstudio.com/";

/// <summary>
/// Default endpoint for Live Metrics (aka QuickPulse).
/// </summary>
internal const string DefaultLiveEndpoint = "https://rt.services.visualstudio.com/";

/// <summary>
/// Sub-domain for Ingestion endpoint (aka Breeze). (https://dc.applicationinsights.azure.com/).
/// </summary>
internal const string IngestionPrefix = "dc";

/// <summary>
/// Sub-domain for Live Metrics endpoint (aka QuickPulse). (https://live.applicationinsights.azure.com/).
/// </summary>
internal const string LivePrefix = "live";

/// <summary>
/// This is the key that a customer would use to specify an explicit endpoint in the connection string.
/// </summary>
internal const string IngestionExplicitEndpointKey = "IngestionEndpoint";

/// <summary>
/// This is the key that a customer would use to specify an explicit endpoint in the connection string.
/// </summary>
internal const string LiveExplicitEndpointKey = "LiveEndpoint";

/// <summary>
/// This is the key that a customer would use to specify an instrumentation key in the connection string.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
Expand All @@ -27,6 +28,7 @@ public void TestInstrumentationKey_IsRequired()
Assert.Throws<InvalidOperationException>(() => RunTest(
connectionString: "EndpointSuffix=ingestion.azuremonitor.com",
expectedIngestionEndpoint: null,
expectedLiveEndpoint: null,
expectedInstrumentationKey: null));
}

Expand All @@ -36,6 +38,7 @@ public void TestInstrumentationKey_CannotBeEmpty()
Assert.Throws<InvalidOperationException>(() => RunTest(
connectionString: "InstrumentationKey=;EndpointSuffix=ingestion.azuremonitor.com",
expectedIngestionEndpoint: null,
expectedLiveEndpoint: null,
expectedInstrumentationKey: null));
}

Expand All @@ -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");
}

Expand All @@ -54,15 +58,17 @@ 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");
}

[Fact]
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");
}

Expand All @@ -72,95 +78,90 @@ 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");
}

[Fact]
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");
}

[Fact]
public void TestExplicitOverride_InvalidValue()
{
Assert.Throws<InvalidOperationException>(() => 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<InvalidOperationException>(() => 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<InvalidOperationException>(() => 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<InvalidOperationException>(() => 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<InvalidOperationException>(() => RunTest(
connectionString: new string('*', Constants.ConnectionStringMaxLength + 1),
expectedIngestionEndpoint: null,
expectedInstrumentationKey: null));
connectionString: new string('*', Constants.ConnectionStringMaxLength + 1)));
}

[Fact]
public void TestParseConnectionString_Empty()
{
Assert.Throws<InvalidOperationException>(() => RunTest(
connectionString: "",
expectedIngestionEndpoint: null,
expectedInstrumentationKey: null));
connectionString: ""));
}

[Fact]
public void TestEndpointProvider_NoInstrumentationKey()
{
Assert.Throws<InvalidOperationException>(() => 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);
}
Expand Down

0 comments on commit 06eeb38

Please sign in to comment.