-
Notifications
You must be signed in to change notification settings - Fork 78
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
3 new functions: DryRun script execution
Added DryRun Functions and DataServiceFactory
- Loading branch information
Showing
20 changed files
with
655 additions
and
113 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
// Copyright (c) Microsoft. All rights reserved. | ||
|
||
using MonitoringFunctions.Providers; | ||
using System; | ||
|
||
namespace MonitoringFunctions | ||
{ | ||
internal sealed class DataServiceFactory | ||
{ | ||
public IDataService GetDataService() | ||
{ | ||
string? environment = Environment.GetEnvironmentVariable("AZURE_FUNCTIONS_ENVIRONMENT"); | ||
|
||
if(environment == "Development") | ||
{ | ||
return new DummyDataService(); | ||
} | ||
else | ||
{ | ||
return new KustoDataService(); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
36 changes: 36 additions & 0 deletions
36
src/MonitoringFunctions/DataService/Kusto/DirectJsonMappingResolver.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
// Copyright (c) Microsoft. All rights reserved. | ||
|
||
using Kusto.Data.Common; | ||
using Newtonsoft.Json.Serialization; | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
|
||
namespace MonitoringFunctions.DataService.Kusto | ||
{ | ||
/// <summary> | ||
/// Creates column mapping for objects whose json property names directly match the column name in the corresponding Kusto table. | ||
/// </summary> | ||
internal sealed class DirectJsonMappingResolver : IJsonColumnMappingResolver | ||
{ | ||
public IEnumerable<ColumnMapping> GetColumnMappings<TModel>() where TModel : IKustoTableRow | ||
{ | ||
DefaultContractResolver contractResolver = new DefaultContractResolver(); | ||
JsonObjectContract? contract = contractResolver.ResolveContract(typeof(TModel)) as JsonObjectContract; | ||
|
||
if (contract == null) | ||
{ | ||
throw new ArgumentException($"Failed to resolve contract. Automatic column mapping is not possible with this type {typeof(TModel)}."); | ||
} | ||
|
||
foreach (JsonProperty property in contract.Properties.Where(p => !p.Ignored && p.Readable)) | ||
{ | ||
yield return new ColumnMapping() | ||
{ | ||
ColumnName = property.PropertyName, | ||
Properties = new Dictionary<string, string>() { { "Path", $"$.{property.PropertyName}" } } | ||
}; | ||
} | ||
} | ||
} | ||
} |
18 changes: 18 additions & 0 deletions
18
src/MonitoringFunctions/DataService/Kusto/IJsonColumnMappingResolver.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
// Copyright (c) Microsoft. All rights reserved. | ||
|
||
using Kusto.Data.Common; | ||
using System.Collections.Generic; | ||
|
||
namespace MonitoringFunctions.DataService.Kusto | ||
{ | ||
internal interface IJsonColumnMappingResolver | ||
{ | ||
/// <summary> | ||
/// Returns Kusto column mapping objects for the given type where each column in Kusto table is mapped | ||
/// to a property when data of the given type is serialized to JSON. | ||
/// </summary> | ||
/// <typeparam name="T">The model class that will be mapped to a Kusto table after serialization.</typeparam> | ||
/// <returns>Column mapping for each of the columns in the Kusto table</returns> | ||
IEnumerable<ColumnMapping> GetColumnMappings<T>() where T : IKustoTableRow; | ||
} | ||
} |
12 changes: 12 additions & 0 deletions
12
src/MonitoringFunctions/DataService/Kusto/IKustoTableRow.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
// Copyright (c) Microsoft. All rights reserved. | ||
|
||
namespace MonitoringFunctions.DataService.Kusto | ||
{ | ||
/// <summary> | ||
/// This interface marks model classes that can be inserted into a kusto table as a row. | ||
/// </summary> | ||
internal interface IKustoTableRow | ||
{ | ||
|
||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
// Copyright (c) Microsoft. All rights reserved. | ||
|
||
using Kusto.Data; | ||
using Kusto.Data.Common; | ||
using Kusto.Ingest; | ||
using Newtonsoft.Json; | ||
using System.Collections.Generic; | ||
using System.IO; | ||
using System.Text; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
|
||
namespace MonitoringFunctions.DataService.Kusto | ||
{ | ||
internal sealed class KustoTable<T> where T : IKustoTableRow | ||
{ | ||
private KustoConnectionStringBuilder _connectionStringBuilder; | ||
|
||
private KustoQueuedIngestionProperties _ingestionProperties; | ||
|
||
public KustoTable(KustoConnectionStringBuilder connectionStringBuilder, string databaseName, string tableName, IJsonColumnMappingResolver columnMappingResolver) | ||
: this(connectionStringBuilder, databaseName, tableName, columnMappingResolver.GetColumnMappings<T>()) | ||
{ | ||
|
||
} | ||
|
||
public KustoTable(KustoConnectionStringBuilder connectionStringBuilder, string databaseName, string tableName, IEnumerable<ColumnMapping> columnMappings) | ||
{ | ||
_connectionStringBuilder = connectionStringBuilder; | ||
|
||
_ingestionProperties = new KustoQueuedIngestionProperties(databaseName, tableName) | ||
{ | ||
ReportLevel = IngestionReportLevel.FailuresOnly, | ||
ReportMethod = IngestionReportMethod.Queue, | ||
IngestionMapping = new IngestionMapping() | ||
{ | ||
IngestionMappingKind = global::Kusto.Data.Ingestion.IngestionMappingKind.Json, | ||
IngestionMappings = columnMappings | ||
}, | ||
Format = DataSourceFormat.json | ||
}; | ||
} | ||
|
||
public async Task InsertRowAsync(T row, CancellationToken cancellationToken = default) | ||
{ | ||
using IKustoQueuedIngestClient ingestClient = KustoIngestFactory.CreateQueuedIngestClient(_connectionStringBuilder); | ||
|
||
string serializedData = JsonConvert.SerializeObject(row); | ||
byte[] serializedBytes = Encoding.UTF8.GetBytes(serializedData); | ||
|
||
using MemoryStream dataStream = new MemoryStream(serializedBytes); | ||
|
||
// IKustoQueuedIngestClient doesn't support cancellation at the moment. Update the line below if it does in the future. | ||
await ingestClient.IngestFromStreamAsync(dataStream, _ingestionProperties, leaveOpen: true); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
78 changes: 78 additions & 0 deletions
78
src/MonitoringFunctions/DataService/Providers/KustoDataService.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
// Copyright (c) Microsoft. All rights reserved. | ||
|
||
using Kusto.Data; | ||
using MonitoringFunctions.DataService.Kusto; | ||
using MonitoringFunctions.Models; | ||
using System; | ||
using System.Net.Http; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
|
||
namespace MonitoringFunctions | ||
{ | ||
internal sealed class KustoDataService : IDataService | ||
{ | ||
private const string ServiceNameAndRegion = "dotnetinstallcluster.eastus2"; | ||
private const string DatabaseName = "dotnet_install_monitoring_database"; | ||
|
||
private readonly KustoTable<HttpRequestLogEntry> _httpRequestLogsTable; | ||
private readonly KustoTable<ScriptExecutionLogEntry> _scriptExecutionLogsTable; | ||
|
||
internal KustoDataService() | ||
{ | ||
KustoConnectionStringBuilder kcsb = new KustoConnectionStringBuilder($"https://ingest-{ServiceNameAndRegion}.kusto.windows.net") | ||
.WithAadManagedIdentity("system"); | ||
|
||
DirectJsonMappingResolver directJsonMappingResolver = new DirectJsonMappingResolver(); | ||
|
||
_httpRequestLogsTable = new KustoTable<HttpRequestLogEntry>(kcsb, DatabaseName, "UrlAccessLogs", directJsonMappingResolver); | ||
_scriptExecutionLogsTable = new KustoTable<ScriptExecutionLogEntry>(kcsb, DatabaseName, "ScriptExecLogs", directJsonMappingResolver); | ||
} | ||
|
||
/// <summary> | ||
/// Reports the details of the <see cref="HttpResponseMessage"/> to kusto. | ||
/// </summary> | ||
/// <param name="monitorName">Name of the monitor generating this data entry.</param> | ||
/// <param name="httpResponse">Response to be reported.</param> | ||
/// <returns>A task, tracking this async operation.</returns> | ||
public async Task ReportUrlAccessAsync(string monitorName, HttpResponseMessage httpResponse, CancellationToken cancellationToken = default) | ||
{ | ||
HttpRequestLogEntry logEntry = new HttpRequestLogEntry() | ||
{ | ||
MonitorName = monitorName, | ||
EventTime = DateTime.UtcNow, | ||
RequestedUrl = httpResponse.RequestMessage.RequestUri.AbsoluteUri, | ||
HttpResponseCode = (int)httpResponse.StatusCode | ||
}; | ||
|
||
await _httpRequestLogsTable.InsertRowAsync(logEntry, cancellationToken).ConfigureAwait(false); | ||
} | ||
|
||
/// <summary> | ||
/// Reports the details of a script execution to kusto. | ||
/// </summary> | ||
/// <param name="monitorName">Name of the monitor generating this data entry.</param> | ||
/// <param name="scriptName">Name of the script that was executed.</param> | ||
/// <param name="commandLineArgs">Command line arguments passed to the script at the moment of execution.</param> | ||
/// <param name="error">Errors that occured during the execution, if any.</param> | ||
/// <returns>A task, tracking this async operation.</returns> | ||
public async Task ReportScriptExecutionAsync(string monitorName, string scriptName, string commandLineArgs, string error, CancellationToken cancellationToken = default) | ||
{ | ||
ScriptExecutionLogEntry logEntry = new ScriptExecutionLogEntry() | ||
{ | ||
MonitorName = monitorName, | ||
EventTime = DateTime.UtcNow, | ||
ScriptName = scriptName, | ||
CommandLineArgs = commandLineArgs, | ||
Error = error | ||
}; | ||
|
||
await _scriptExecutionLogsTable.InsertRowAsync(logEntry, cancellationToken).ConfigureAwait(false); | ||
} | ||
|
||
public void Dispose() | ||
{ | ||
// Do nothing | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
// Copyright (c) Microsoft. All rights reserved. | ||
|
||
using System.IO; | ||
using System.Threading.Tasks; | ||
using Microsoft.Azure.WebJobs; | ||
using Microsoft.Extensions.Logging; | ||
using MonitoringFunctions.Models; | ||
using Kusto.Cloud.Platform.IO; | ||
using System.Threading; | ||
|
||
namespace MonitoringFunctions.Functions | ||
{ | ||
/// <summary> | ||
/// Runs the scripts in -DryRun mode and checks weather the generated links are accessible | ||
/// </summary> | ||
internal static class DryRunUrlChecker | ||
{ | ||
[FunctionName("DryRunLTS")] | ||
public static async Task RunLTSAsync([TimerTrigger("0 */30 * * * *")] TimerInfo myTimer, ILogger log) | ||
{ | ||
string monitorName = "dry_run_LTS"; | ||
string cmdArgs = "-c LTS"; | ||
|
||
await ExecuteDryRunCheckAndReportUrlAccessAsync(log, monitorName, cmdArgs).ConfigureAwait(false); | ||
} | ||
|
||
[FunctionName("DryRun3_1")] | ||
public static async Task Run3_1Async([TimerTrigger("0 */30 * * * *")] TimerInfo myTimer, ILogger log) | ||
{ | ||
string monitorName = "dry_run_3_1"; | ||
string cmdArgs = "-c 3.1"; | ||
|
||
await ExecuteDryRunCheckAndReportUrlAccessAsync(log, monitorName, cmdArgs).ConfigureAwait(false); | ||
} | ||
|
||
[FunctionName("DryRun3_0Runtime")] | ||
public static async Task Run3_0RuntimeAsync([TimerTrigger("0 */30 * * * *")] TimerInfo myTimer, ILogger log) | ||
{ | ||
string monitorName = "dry_run_3_0_runtime"; | ||
string cmdArgs = "-c 3.0 -Runtime dotnet"; | ||
|
||
await ExecuteDryRunCheckAndReportUrlAccessAsync(log, monitorName, cmdArgs).ConfigureAwait(false); | ||
} | ||
|
||
/// <summary> | ||
/// Executes the Ps1 script with DryDun switch, | ||
/// Parses the output to acquire primary and legacy Urls, | ||
/// Tests the primary Url to see if it is available, | ||
/// Reports the results to the data service provided. | ||
/// </summary> | ||
internal static async Task ExecuteDryRunCheckAndReportUrlAccessAsync(ILogger log, string monitorName, string additionalCmdArgs, | ||
CancellationToken cancellationToken = default) | ||
{ | ||
string scriptName = "dotnet-install.ps1"; | ||
string commandLineArgs = $"-DryRun {additionalCmdArgs}"; | ||
|
||
using IDataService dataService = new DataServiceFactory().GetDataService(); | ||
|
||
// Execute the script; | ||
ScriptExecutionResult results = await HelperMethods.ExecuteInstallScriptPs1Async(commandLineArgs).ConfigureAwait(false); | ||
|
||
log.LogInformation($"Ouput stream: {results.Output}"); | ||
|
||
if (!string.IsNullOrWhiteSpace(results.Error)) | ||
{ | ||
log.LogError($"Error stream: {results.Error}"); | ||
await dataService.ReportScriptExecutionAsync(monitorName, scriptName, commandLineArgs, results.Error, cancellationToken) | ||
.ConfigureAwait(false); | ||
return; | ||
} | ||
|
||
// Parse the output | ||
ScriptDryRunResult dryRunResults = ParseDryRunOutput(results.Output); | ||
|
||
if (string.IsNullOrWhiteSpace(dryRunResults.PrimaryUrl)) | ||
{ | ||
log.LogError($"Primary Url was not found for channel {additionalCmdArgs}"); | ||
await dataService.ReportScriptExecutionAsync(monitorName, scriptName, commandLineArgs, | ||
"Failed to parse primary url from the following DryRun execution output: " + results.Output | ||
, cancellationToken).ConfigureAwait(false); | ||
return; | ||
} | ||
|
||
// Validate URL accessibility | ||
await HelperMethods.CheckAndReportUrlAccessAsync(log, monitorName, dryRunResults.PrimaryUrl, dataService); | ||
} | ||
|
||
/// <summary> | ||
/// Parses the output of the script when executed in DryRun mode and finds out Primary and Legacy urls. | ||
/// </summary> | ||
/// <param name="output">Output of the script execution in DryRun mode</param> | ||
/// <returns>Object containing primary and legacy runs</returns> | ||
internal static ScriptDryRunResult ParseDryRunOutput(string? output) | ||
{ | ||
string primaryUrlIdentifier = "Primary named payload URL: "; | ||
string legacyUrlIdentifier = "Legacy named payload URL: "; | ||
|
||
ScriptDryRunResult result = new ScriptDryRunResult(); | ||
|
||
using StringStream stringStream = new StringStream(output); | ||
using StreamReader streamReader = new StreamReader(stringStream); | ||
|
||
string? line; | ||
while ((line = streamReader.ReadLine()) != null) | ||
{ | ||
// Does this line contain the primary url? | ||
int primaryIdIndex = line.IndexOf(primaryUrlIdentifier); | ||
if (primaryIdIndex != -1) | ||
{ | ||
result.PrimaryUrl = line.Substring(primaryIdIndex + primaryUrlIdentifier.Length); | ||
} | ||
else | ||
{ | ||
// Does this line contain the legacy url? | ||
int legacyIdIndex = line.IndexOf(legacyUrlIdentifier); | ||
if(legacyIdIndex != -1) | ||
{ | ||
result.LegacyUrl = line.Substring(legacyIdIndex + legacyUrlIdentifier.Length); | ||
} | ||
} | ||
} | ||
|
||
return result; | ||
} | ||
} | ||
} |
Oops, something went wrong.