diff --git a/src/ApiService/ApiService/OneFuzzTypes/Enums.cs b/src/ApiService/ApiService/OneFuzzTypes/Enums.cs index 1f5868caa2..389c0eddc0 100644 --- a/src/ApiService/ApiService/OneFuzzTypes/Enums.cs +++ b/src/ApiService/ApiService/OneFuzzTypes/Enums.cs @@ -165,7 +165,7 @@ public static class ScalesetStateHelper { public static class VmStateHelper { - private static readonly IReadOnlySet _needsWork = new HashSet { VmState.Init, VmState.Init, VmState.ExtensionsLaunch, VmState.Stopping }; + private static readonly IReadOnlySet _needsWork = new HashSet { VmState.Init, VmState.ExtensionsLaunch, VmState.Stopping }; private static readonly IReadOnlySet _available = new HashSet { VmState.Init, VmState.ExtensionsLaunch, VmState.ExtensionsFailed, VmState.VmAllocationFailed, VmState.Running, }; public static IReadOnlySet NeedsWork => _needsWork; diff --git a/src/ApiService/ApiService/OneFuzzTypes/Model.cs b/src/ApiService/ApiService/OneFuzzTypes/Model.cs index 01d055db30..d3d40b8f37 100644 --- a/src/ApiService/ApiService/OneFuzzTypes/Model.cs +++ b/src/ApiService/ApiService/OneFuzzTypes/Model.cs @@ -147,7 +147,12 @@ public record Proxy bool Outdated ) : StatefulEntityBase(State); -public record Error(ErrorCode Code, string[]? Errors = null); +public record Error(ErrorCode Code, string[]? Errors = null) { + public sealed override string ToString() { + var errorsString = Errors != null ? string.Join("", Errors) : string.Empty; + return $"Error {{ Code = {Code}, Errors = {errorsString} }}"; + } +}; public record UserInfo(Guid? ApplicationId, Guid? ObjectId, String? Upn); @@ -413,7 +418,7 @@ NotificationTemplate Config public record BlobRef( string Account, Container container, - string name + string Name ); public record Report( @@ -436,7 +441,7 @@ public record Report( string? MinimizedStackFunctionNamesSha256, List? MinimizedStackFunctionLines, string? MinimizedStackFunctionLinesSha256 -); +) : IReport; public record NoReproReport( string InputSha, @@ -444,7 +449,7 @@ public record NoReproReport( string? Executable, Guid TaskId, Guid JobId, - int Tries, + long Tries, string? Error ); @@ -456,7 +461,7 @@ public record CrashTestResult( public record RegressionReport( CrashTestResult CrashTestResult, CrashTestResult? OriginalCrashTestResult -); +) : IReport; public record NotificationTemplate( AdoTemplate? AdoTemplate, @@ -471,8 +476,7 @@ public record TeamsTemplate(); public record GithubIssuesTemplate(); public record Repro( - [PartitionKey] Guid VmId, - [RowKey] Guid _, + [PartitionKey][RowKey] Guid VmId, Guid TaskId, ReproConfig Config, VmState State, diff --git a/src/ApiService/ApiService/onefuzzlib/Creds.cs b/src/ApiService/ApiService/onefuzzlib/Creds.cs index bb99642ffd..5a898b729c 100644 --- a/src/ApiService/ApiService/onefuzzlib/Creds.cs +++ b/src/ApiService/ApiService/onefuzzlib/Creds.cs @@ -28,6 +28,10 @@ public interface ICreds { public Uri GetInstanceUrl(); public Async.Task GetScalesetPrincipalId(); public Async.Task QueryMicrosoftGraph(HttpMethod method, string resource); + + public GenericResource ParseResourceId(string resourceId); + + public Async.Task GetData(GenericResource resource); } public class Creds : ICreds { @@ -145,6 +149,17 @@ public async Task QueryMicrosoftGraph(HttpMethod method, string resource) throw new GraphQueryException($"request did not succeed: HTTP {response.StatusCode} - {errorText}"); } } + + public GenericResource ParseResourceId(string resourceId) { + return ArmClient.GetGenericResource(new ResourceIdentifier(resourceId)); + } + + public async Async.Task GetData(GenericResource resource) { + if (!resource.HasData) { + return await resource.GetAsync(); + } + return resource; + } } class GraphQueryException : Exception { diff --git a/src/ApiService/ApiService/onefuzzlib/NotificationOperations.cs b/src/ApiService/ApiService/onefuzzlib/NotificationOperations.cs index 5f205fac6a..60b5328cd8 100644 --- a/src/ApiService/ApiService/onefuzzlib/NotificationOperations.cs +++ b/src/ApiService/ApiService/onefuzzlib/NotificationOperations.cs @@ -19,7 +19,7 @@ public NotificationOperations(ILogTracer log, IOnefuzzContext context) public async Async.Task NewFiles(Container container, string filename, bool failTaskOnTransientError) { var notifications = GetNotifications(container); var hasNotifications = await notifications.AnyAsync(); - var report = await _context.Reports.GetReportOrRegression(container, filename, expectReports: hasNotifications); + var reportOrRegression = await _context.Reports.GetReportOrRegression(container, filename, expectReports: hasNotifications); if (!hasNotifications) { return; @@ -34,19 +34,19 @@ public async Async.Task NewFiles(Container container, string filename, bool fail done.Add(notification.Config); if (notification.Config.TeamsTemplate != null) { - NotifyTeams(notification.Config.TeamsTemplate, container, filename, report); + NotifyTeams(notification.Config.TeamsTemplate, container, filename, reportOrRegression!); } - if (report == null) { + if (reportOrRegression == null) { continue; } if (notification.Config.AdoTemplate != null) { - NotifyAdo(notification.Config.AdoTemplate, container, filename, report, failTaskOnTransientError); + NotifyAdo(notification.Config.AdoTemplate, container, filename, reportOrRegression, failTaskOnTransientError); } if (notification.Config.GithubIssuesTemplate != null) { - GithubIssue(notification.Config.GithubIssuesTemplate, container, filename, report); + GithubIssue(notification.Config.GithubIssuesTemplate, container, filename, reportOrRegression); } } @@ -58,15 +58,17 @@ public async Async.Task NewFiles(Container container, string filename, bool fail } } - if (report?.Report != null) { - var reportTask = await _context.TaskOperations.GetByJobIdAndTaskId(report.Report.JobId, report.Report.TaskId); + if (reportOrRegression is Report) { + var report = (reportOrRegression as Report)!; + var reportTask = await _context.TaskOperations.GetByJobIdAndTaskId(report.JobId, report.TaskId); - var crashReportedEvent = new EventCrashReported(report.Report, container, filename, reportTask?.Config); + var crashReportedEvent = new EventCrashReported(report, container, filename, reportTask?.Config); await _context.Events.SendEvent(crashReportedEvent); - } else if (report?.RegressionReport != null) { - var reportTask = await GetRegressionReportTask(report.RegressionReport); + } else if (reportOrRegression is RegressionReport) { + var regressionReport = (reportOrRegression as RegressionReport)!; + var reportTask = await GetRegressionReportTask(regressionReport); - var regressionEvent = new EventRegressionReported(report.RegressionReport, container, filename, reportTask?.Config); + var regressionEvent = new EventRegressionReported(regressionReport, container, filename, reportTask?.Config); await _context.Events.SendEvent(regressionEvent); } else { await _context.Events.SendEvent(new EventFileAdded(container, filename)); @@ -96,15 +98,15 @@ public IAsyncEnumerable GetNotifications(Container container) { return null; } - private void GithubIssue(GithubIssuesTemplate config, Container container, string filename, RegressionReportOrReport? report) { + private void GithubIssue(GithubIssuesTemplate config, Container container, string filename, IReport report) { throw new NotImplementedException(); } - private void NotifyAdo(AdoTemplate config, Container container, string filename, RegressionReportOrReport report, bool failTaskOnTransientError) { + private void NotifyAdo(AdoTemplate config, Container container, string filename, IReport report, bool failTaskOnTransientError) { throw new NotImplementedException(); } - private void NotifyTeams(TeamsTemplate config, Container container, string filename, RegressionReportOrReport? report) { + private void NotifyTeams(TeamsTemplate config, Container container, string filename, IReport report) { throw new NotImplementedException(); } } diff --git a/src/ApiService/ApiService/onefuzzlib/Reports.cs b/src/ApiService/ApiService/onefuzzlib/Reports.cs index 42fda88b61..917ed3979c 100644 --- a/src/ApiService/ApiService/onefuzzlib/Reports.cs +++ b/src/ApiService/ApiService/onefuzzlib/Reports.cs @@ -4,7 +4,7 @@ namespace Microsoft.OneFuzz.Service; public interface IReports { - public Async.Task GetReportOrRegression(Container container, string fileName, bool expectReports = false, params string[] args); + public Async.Task GetReportOrRegression(Container container, string fileName, bool expectReports = false, params string[] args); } public class Reports : IReports { @@ -15,7 +15,7 @@ public Reports(ILogTracer log, IContainers containers) { _containers = containers; } - public async Async.Task GetReportOrRegression(Container container, string fileName, bool expectReports = false, params string[] args) { + public async Async.Task GetReportOrRegression(Container container, string fileName, bool expectReports = false, params string[] args) { var filePath = String.Join("/", new[] { container.ContainerName, fileName }); if (!fileName.EndsWith(".json", StringComparison.Ordinal)) { if (expectReports) { @@ -36,26 +36,20 @@ public Reports(ILogTracer log, IContainers containers) { return ParseReportOrRegression(blob.ToString(), filePath, expectReports); } - private RegressionReportOrReport? ParseReportOrRegression(string content, string? filePath, bool expectReports = false) { - try { - return new RegressionReportOrReport { - RegressionReport = JsonSerializer.Deserialize(content, EntityConverter.GetJsonSerializerOptions()) - }; - } catch (JsonException e) { - try { - return new RegressionReportOrReport { - Report = JsonSerializer.Deserialize(content, EntityConverter.GetJsonSerializerOptions()) - }; - } catch (JsonException e2) { - if (expectReports) { - _log.Error($"unable to parse report ({filePath}) as a report or regression. regression error: {e.Message} report error: {e2.Message}"); - } + private IReport? ParseReportOrRegression(string content, string? filePath, bool expectReports = false) { + var regressionReport = JsonSerializer.Deserialize(content, EntityConverter.GetJsonSerializerOptions()); + if (regressionReport == null || regressionReport.CrashTestResult == null) { + var report = JsonSerializer.Deserialize(content, EntityConverter.GetJsonSerializerOptions()); + if (expectReports && report == null) { + _log.Error($"unable to parse report ({filePath}) as a report or regression"); return null; } + return report; } + return regressionReport; } - private RegressionReportOrReport? ParseReportOrRegression(IEnumerable content, string? filePath, bool expectReports = false) { + private IReport? ParseReportOrRegression(IEnumerable content, string? filePath, bool expectReports = false) { try { var str = System.Text.Encoding.UTF8.GetString(content.ToArray()); return ParseReportOrRegression(str, filePath, expectReports); @@ -68,7 +62,4 @@ public Reports(ILogTracer log, IContainers containers) { } } -public class RegressionReportOrReport { - public RegressionReport? RegressionReport { get; set; } - public Report? Report { get; set; } -} +public interface IReport { }; diff --git a/src/ApiService/IntegrationTests/Fakes/TestCreds.cs b/src/ApiService/IntegrationTests/Fakes/TestCreds.cs index 4e5a137d62..90293b1588 100644 --- a/src/ApiService/IntegrationTests/Fakes/TestCreds.cs +++ b/src/ApiService/IntegrationTests/Fakes/TestCreds.cs @@ -56,4 +56,12 @@ public Task GetScalesetPrincipalId() { public Task QueryMicrosoftGraph(HttpMethod method, string resource) { throw new NotImplementedException(); } + + public GenericResource ParseResourceId(string resourceId) { + throw new NotImplementedException(); + } + + public Task GetData(GenericResource resource) { + throw new NotImplementedException(); + } } diff --git a/src/ApiService/Tests/OrmModelsTest.cs b/src/ApiService/Tests/OrmModelsTest.cs index 6420792178..ccf58ddcc2 100644 --- a/src/ApiService/Tests/OrmModelsTest.cs +++ b/src/ApiService/Tests/OrmModelsTest.cs @@ -300,6 +300,41 @@ public static Gen Report() { ); } + public static Gen NoReproReport() { + return Arb.Generate>().Select( + arg => + new NoReproReport( + arg.Item1, + arg.Item2, + arg.Item3, + arg.Item4, + arg.Item4, + arg.Item5, + arg.Item3 + ) + ); + } + + public static Gen CrashTestResult() { + return Arb.Generate>().Select( + arg => + new CrashTestResult( + arg.Item1, + arg.Item2 + ) + ); + } + + public static Gen RegressionReport() { + return Arb.Generate>().Select( + arg => + new RegressionReport( + arg.Item1, + arg.Item2 + ) + ); + } + public static Gen Container() { return Arb.Generate>>().Select( arg => new Container(string.Join("", arg.Item1.Get.Where(c => char.IsLetterOrDigit(c) || c == '-'))!) @@ -801,7 +836,7 @@ public bool NotificationTemplate(NotificationTemplate e) { [Property] - public bool RegressionReportOrReport(RegressionReportOrReport e) { + public bool RegressionReport(RegressionReport e) { return Test(e); } diff --git a/src/ApiService/Tests/TimerReproTests.cs b/src/ApiService/Tests/TimerReproTests.cs index 40aea780ae..3bead39c3c 100644 --- a/src/ApiService/Tests/TimerReproTests.cs +++ b/src/ApiService/Tests/TimerReproTests.cs @@ -99,7 +99,6 @@ private static Repro GenerateRepro() { return new Repro( Guid.NewGuid(), Guid.Empty, - Guid.Empty, new ReproConfig( new Container(String.Empty), String.Empty,