From 88b2cb5c58638a310e446fece7d5fa2432c3d10c Mon Sep 17 00:00:00 2001 From: Cheick Keita <chkeita@microsoft.com> Date: Mon, 30 Oct 2023 17:39:50 -0700 Subject: [PATCH 1/3] Add a field to the job summary to indicate if at least one bug was created --- src/ApiService/ApiService/Functions/Jobs.cs | 12 ++++---- .../ApiService/Functions/QueueJobResult.cs | 4 +++ .../ApiService/OneFuzzTypes/Model.cs | 7 +++-- .../ApiService/OneFuzzTypes/Responses.cs | 9 ++++-- src/ApiService/ApiService/Program.cs | 5 ++-- .../ApiService/onefuzzlib/JobCrashReported.cs | 28 +++++++++++++++++++ .../ApiService/onefuzzlib/OnefuzzContext.cs | 3 +- .../onefuzzlib/notifications/Ado.cs | 2 +- .../IntegrationTests/Fakes/TestContext.cs | 25 +++++++++++++++++ src/ApiService/IntegrationTests/JobsTests.cs | 21 ++++++++++++++ src/pytypes/onefuzztypes/models.py | 1 + 11 files changed, 101 insertions(+), 16 deletions(-) create mode 100644 src/ApiService/ApiService/onefuzzlib/JobCrashReported.cs diff --git a/src/ApiService/ApiService/Functions/Jobs.cs b/src/ApiService/ApiService/Functions/Jobs.cs index 3f8746df1f..f4b5f04fa9 100644 --- a/src/ApiService/ApiService/Functions/Jobs.cs +++ b/src/ApiService/ApiService/Functions/Jobs.cs @@ -136,13 +136,11 @@ private async Task<HttpResponseData> Get(HttpRequestData req) { static JobTaskInfo TaskToJobTaskInfo(Task t) => new(t.TaskId, t.Config.Task.Type, t.State); var tasks = _context.TaskOperations.SearchStates(jobId); - if (search.WithTasks ?? false) { - var ts = await tasks.ToListAsync(); - return await RequestHandling.Ok(req, JobResponse.ForJob(job, ts)); - } else { - var taskInfo = await tasks.Select(TaskToJobTaskInfo).ToListAsync(); - return await RequestHandling.Ok(req, JobResponse.ForJob(job, taskInfo)); - } + + IAsyncEnumerable<IJobTaskInfo> taskInfo = search.WithTasks ?? false ? tasks : tasks.Select(TaskToJobTaskInfo); + + var crashReported = await _context.JobCrashReportedOperations.CrashReported(jobId); + return await RequestHandling.Ok(req, JobResponse.ForJob(job, taskInfo.ToEnumerable(), crashReported)); } var jobs = await _context.JobOperations.SearchState(states: search.State ?? Enumerable.Empty<JobState>()).ToListAsync(); diff --git a/src/ApiService/ApiService/Functions/QueueJobResult.cs b/src/ApiService/ApiService/Functions/QueueJobResult.cs index 31b39802d6..3f863cb2f2 100644 --- a/src/ApiService/ApiService/Functions/QueueJobResult.cs +++ b/src/ApiService/ApiService/Functions/QueueJobResult.cs @@ -49,6 +49,10 @@ public async Async.Task Run([QueueTrigger("job-result", Connection = "AzureWebJo var jobResultType = data.Type; _log.LogInformation($"job result data type: {jobResultType}"); + if (jobResultType == "CrashReported") { + var _result = await _context.JobCrashReportedOperations.ReportCrash(job.JobId, jr.TaskId); + } + Dictionary<string, double> value; if (jr.Value.Count > 0) { value = jr.Value; diff --git a/src/ApiService/ApiService/OneFuzzTypes/Model.cs b/src/ApiService/ApiService/OneFuzzTypes/Model.cs index 4dd4000283..38b4c19938 100644 --- a/src/ApiService/ApiService/OneFuzzTypes/Model.cs +++ b/src/ApiService/ApiService/OneFuzzTypes/Model.cs @@ -968,9 +968,12 @@ public record Job( StoredUserInfo? UserInfo, string? Error = null, DateTimeOffset? EndTime = null -) : StatefulEntityBase<JobState>(State) { +) : StatefulEntityBase<JobState>(State); -} +public record JobCrashReported( + [PartitionKey] Guid JobId, + [RowKey] Guid TaskId +) : EntityBase; // This is like UserInfo but lacks the UPN: public record StoredUserInfo(Guid? ApplicationId, Guid? ObjectId); diff --git a/src/ApiService/ApiService/OneFuzzTypes/Responses.cs b/src/ApiService/ApiService/OneFuzzTypes/Responses.cs index c1067305ad..2760837a66 100644 --- a/src/ApiService/ApiService/OneFuzzTypes/Responses.cs +++ b/src/ApiService/ApiService/OneFuzzTypes/Responses.cs @@ -92,6 +92,7 @@ public record ContainerInfo( Uri SasUrl ) : BaseResponse(); + public record JobResponse( Guid JobId, JobState State, @@ -101,10 +102,11 @@ public record JobResponse( IEnumerable<IJobTaskInfo>? TaskInfo, StoredUserInfo? UserInfo, [property: JsonPropertyName("Timestamp")] // must retain capital T for backcompat - DateTimeOffset? Timestamp + DateTimeOffset? Timestamp, + bool CrashReported // not including UserInfo from Job model ) : BaseResponse() { - public static JobResponse ForJob(Job j, IEnumerable<IJobTaskInfo>? taskInfo) + public static JobResponse ForJob(Job j, IEnumerable<IJobTaskInfo>? taskInfo, bool crashReported = false) => new( JobId: j.JobId, State: j.State, @@ -113,7 +115,8 @@ public static JobResponse ForJob(Job j, IEnumerable<IJobTaskInfo>? taskInfo) EndTime: j.EndTime, TaskInfo: taskInfo, UserInfo: j.UserInfo, - Timestamp: j.Timestamp + Timestamp: j.Timestamp, + CrashReported: crashReported ); public DateTimeOffset? StartTime => EndTime is DateTimeOffset endTime ? endTime.Subtract(TimeSpan.FromHours(Config.Duration)) : null; } diff --git a/src/ApiService/ApiService/Program.cs b/src/ApiService/ApiService/Program.cs index f26463883b..112b2d358c 100644 --- a/src/ApiService/ApiService/Program.cs +++ b/src/ApiService/ApiService/Program.cs @@ -24,11 +24,11 @@ namespace Microsoft.OneFuzz.Service; public class Program { /// <summary> - /// + /// /// </summary> public class LoggingMiddleware : IFunctionsWorkerMiddleware { /// <summary> - /// + /// /// </summary> /// <param name="context"></param> /// <param name="next"></param> @@ -198,6 +198,7 @@ public static async Async.Task Main() { .AddScoped<INodeMessageOperations, NodeMessageOperations>() .AddScoped<ISubnet, Subnet>() .AddScoped<IAutoScaleOperations, AutoScaleOperations>() + .AddScoped<IJobCrashReportedOperations, JobCrashReportedOperations>() .AddSingleton<GraphServiceClient>(new GraphServiceClient(new DefaultAzureCredential())) .AddSingleton<DependencyTrackingTelemetryModule>() .AddSingleton<ICreds, Creds>() diff --git a/src/ApiService/ApiService/onefuzzlib/JobCrashReported.cs b/src/ApiService/ApiService/onefuzzlib/JobCrashReported.cs new file mode 100644 index 0000000000..e0b903979b --- /dev/null +++ b/src/ApiService/ApiService/onefuzzlib/JobCrashReported.cs @@ -0,0 +1,28 @@ +using System.Threading.Tasks; +using ApiService.OneFuzzLib.Orm; +using Microsoft.Extensions.Logging; +namespace Microsoft.OneFuzz.Service; + +public interface IJobCrashReportedOperations : IOrm<JobCrashReported> { + public Task<bool> CrashReported(Guid jobId); + public Task<OneFuzzResultVoid> ReportCrash(Guid jobId, Guid taskId); +} + +public class JobCrashReportedOperations : Orm<JobCrashReported>, IJobCrashReportedOperations { + public JobCrashReportedOperations(ILogger<JobCrashReportedOperations> logTracer, IOnefuzzContext context) : base(logTracer, context) { + } + + public async Task<bool> CrashReported(Guid jobId) { + return await QueryAsync(Query.RowKey(jobId.ToString())).AnyAsync(); + } + + public async Task<OneFuzzResultVoid> ReportCrash(Guid jobId, Guid taskId) { + + var result = await Update(new JobCrashReported(jobId, taskId)); + if (!result.IsOk) { + return OneFuzzResultVoid.Error(ErrorCode.UNABLE_TO_UPDATE, "Failed to update job crash reported"); + } + + return OneFuzzResultVoid.Ok; + } +} diff --git a/src/ApiService/ApiService/onefuzzlib/OnefuzzContext.cs b/src/ApiService/ApiService/onefuzzlib/OnefuzzContext.cs index 03c6322663..3da5f6522e 100644 --- a/src/ApiService/ApiService/onefuzzlib/OnefuzzContext.cs +++ b/src/ApiService/ApiService/onefuzzlib/OnefuzzContext.cs @@ -49,7 +49,7 @@ public interface IOnefuzzContext { ITeams Teams { get; } IGithubIssues GithubIssues { get; } IAdo Ado { get; } - + IJobCrashReportedOperations JobCrashReportedOperations { get; } IFeatureManagerSnapshot FeatureManagerSnapshot { get; } IConfigurationRefresher ConfigurationRefresher { get; } } @@ -101,6 +101,7 @@ public OnefuzzContext(IServiceProvider serviceProvider) { public ITeams Teams => _serviceProvider.GetRequiredService<ITeams>(); public IGithubIssues GithubIssues => _serviceProvider.GetRequiredService<IGithubIssues>(); public IAdo Ado => _serviceProvider.GetRequiredService<IAdo>(); + public IJobCrashReportedOperations JobCrashReportedOperations => _serviceProvider.GetRequiredService<IJobCrashReportedOperations>(); public IFeatureManagerSnapshot FeatureManagerSnapshot => _serviceProvider.GetRequiredService<IFeatureManagerSnapshot>(); diff --git a/src/ApiService/ApiService/onefuzzlib/notifications/Ado.cs b/src/ApiService/ApiService/onefuzzlib/notifications/Ado.cs index b3633f12d4..46ff35f085 100644 --- a/src/ApiService/ApiService/onefuzzlib/notifications/Ado.cs +++ b/src/ApiService/ApiService/onefuzzlib/notifications/Ado.cs @@ -663,8 +663,8 @@ public async Async.Task Process(IList<(string, string)> notificationInfo, bool i _logTracer.AddTags(notificationInfo); _logTracer.AddTag("WorkItemId", entry.Id.HasValue ? entry.Id.Value.ToString() : ""); _logTracer.LogEvent(adoEventType); - } } + } private static bool IsADODuplicateWorkItem(WorkItem wi, Dictionary<string, string>? duplicateFields) { // A work item could have System.State == Resolve && System.Reason == Duplicate diff --git a/src/ApiService/IntegrationTests/Fakes/TestContext.cs b/src/ApiService/IntegrationTests/Fakes/TestContext.cs index 66d121e746..74997f8210 100644 --- a/src/ApiService/IntegrationTests/Fakes/TestContext.cs +++ b/src/ApiService/IntegrationTests/Fakes/TestContext.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Net.Http; using Microsoft.Extensions.Caching.Memory; @@ -42,6 +43,7 @@ public TestContext(IHttpClientFactory httpClientFactory, OneFuzzLoggerProvider p ReproOperations = new ReproOperations(provider.CreateLogger<ReproOperations>(), this); Reports = new Reports(provider.CreateLogger<Reports>(), Containers); NotificationOperations = new NotificationOperations(provider.CreateLogger<NotificationOperations>(), this); + JobCrashReportedOperations = new JobCrashReportedOperations(provider.CreateLogger<JobCrashReportedOperations>(), this); FeatureManagerSnapshot = new TestFeatureManagerSnapshot(); WebhookOperations = new TestWebhookOperations(httpClientFactory, provider.CreateLogger<WebhookOperations>(), this); @@ -65,9 +67,28 @@ public Async.Task InsertAll(params EntityBase[] objs) InstanceConfig ic => ConfigOperations.Insert(ic), Notification n => NotificationOperations.Insert(n), Webhook w => WebhookOperations.Insert(w), + JobCrashReported crashReported => JobCrashReportedOperations.Insert(crashReported), _ => throw new NotSupportedException($"You will need to add an TestContext.InsertAll case for {x.GetType()} entities"), })); + public Async.Task InsertAll(IEnumerable<EntityBase> objs) + => Async.Task.WhenAll( + objs.Select(x => x switch { + Task t => TaskOperations.Insert(t), + Node n => NodeOperations.Insert(n), + Pool p => PoolOperations.Insert(p), + Job j => JobOperations.Insert(j), + JobResult jr => JobResultOperations.Insert(jr), + Repro r => ReproOperations.Insert(r), + Scaleset ss => ScalesetOperations.Insert(ss), + NodeTasks nt => NodeTasksOperations.Insert(nt), + InstanceConfig ic => ConfigOperations.Insert(ic), + Notification n => NotificationOperations.Insert(n), + Webhook w => WebhookOperations.Insert(w), + JobCrashReported crashReported => JobCrashReportedOperations.Insert(crashReported), + _ => throw new NotSupportedException($"You will need to add an TestContext.InsertAll case for {x.GetType()} entities"), + })); + // Implementations: public IMemoryCache Cache { get; } @@ -109,6 +130,8 @@ public Async.Task InsertAll(params EntityBase[] objs) public IWebhookMessageLogOperations WebhookMessageLogOperations { get; } + public IJobCrashReportedOperations JobCrashReportedOperations {get;} + // -- Remainder not implemented -- public IConfig Config => throw new System.NotImplementedException(); @@ -143,4 +166,6 @@ public Async.Task InsertAll(params EntityBase[] objs) public IAdo Ado => throw new NotImplementedException(); public IConfigurationRefresher ConfigurationRefresher => throw new NotImplementedException(); + + } diff --git a/src/ApiService/IntegrationTests/JobsTests.cs b/src/ApiService/IntegrationTests/JobsTests.cs index 28dbe8457f..857b54bd64 100644 --- a/src/ApiService/IntegrationTests/JobsTests.cs +++ b/src/ApiService/IntegrationTests/JobsTests.cs @@ -226,4 +226,25 @@ await Context.InsertAll( Assert.Equal(task.Config.Task.Type, returnedTasks[0].Type); } + + [Fact] + public async Async.Task Get_CanFindSpecificJobWithBugs() { + var taskConfig = new TaskConfig(_jobId, new List<Guid>(), new TaskDetails(TaskType.Coverage, 60)); + await Context.InsertAll( + new Job(_jobId, JobState.Stopped, _config, null), + new Task(_jobId, Guid.NewGuid(), TaskState.Running, Os.Windows, taskConfig), + new JobCrashReported(_jobId, Guid.NewGuid()) + ); + + var func = new Jobs(Context, LoggerProvider.CreateLogger<Jobs>()); + + var ctx = new TestFunctionContext(); + var result = await func.Run(TestHttpRequestData.FromJson("GET", new JobSearch(JobId: _jobId)), ctx); + Assert.Equal(HttpStatusCode.OK, result.StatusCode); + + var response = BodyAs<JobResponse>(result); + Assert.Equal(_jobId, response.JobId); + Assert.NotNull(response.TaskInfo); + Assert.True(response.CrashReported); + } } diff --git a/src/pytypes/onefuzztypes/models.py b/src/pytypes/onefuzztypes/models.py index 746c528c1c..8adae9c9ee 100644 --- a/src/pytypes/onefuzztypes/models.py +++ b/src/pytypes/onefuzztypes/models.py @@ -761,6 +761,7 @@ class Job(BaseModel): task_info: Optional[List[Union[Task, JobTaskInfo]]] user_info: Optional[UserInfo] start_time: Optional[datetime] = None + crash_reported: Optional[bool] = None class NetworkConfig(BaseModel): From 4c8a30066b0c4b06db69b088250f3de4895e3e0f Mon Sep 17 00:00:00 2001 From: Cheick Keita <chkeita@microsoft.com> Date: Mon, 30 Oct 2023 17:45:30 -0700 Subject: [PATCH 2/3] format --- src/ApiService/ApiService/onefuzzlib/notifications/Ado.cs | 2 +- src/ApiService/IntegrationTests/Fakes/TestContext.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ApiService/ApiService/onefuzzlib/notifications/Ado.cs b/src/ApiService/ApiService/onefuzzlib/notifications/Ado.cs index 46ff35f085..b3633f12d4 100644 --- a/src/ApiService/ApiService/onefuzzlib/notifications/Ado.cs +++ b/src/ApiService/ApiService/onefuzzlib/notifications/Ado.cs @@ -663,8 +663,8 @@ public async Async.Task Process(IList<(string, string)> notificationInfo, bool i _logTracer.AddTags(notificationInfo); _logTracer.AddTag("WorkItemId", entry.Id.HasValue ? entry.Id.Value.ToString() : ""); _logTracer.LogEvent(adoEventType); - } } + } private static bool IsADODuplicateWorkItem(WorkItem wi, Dictionary<string, string>? duplicateFields) { // A work item could have System.State == Resolve && System.Reason == Duplicate diff --git a/src/ApiService/IntegrationTests/Fakes/TestContext.cs b/src/ApiService/IntegrationTests/Fakes/TestContext.cs index 74997f8210..ada13f0d44 100644 --- a/src/ApiService/IntegrationTests/Fakes/TestContext.cs +++ b/src/ApiService/IntegrationTests/Fakes/TestContext.cs @@ -130,7 +130,7 @@ public Async.Task InsertAll(IEnumerable<EntityBase> objs) public IWebhookMessageLogOperations WebhookMessageLogOperations { get; } - public IJobCrashReportedOperations JobCrashReportedOperations {get;} + public IJobCrashReportedOperations JobCrashReportedOperations { get; } // -- Remainder not implemented -- From ee4b7123c03a81379e3779b04bc9dc733906a18f Mon Sep 17 00:00:00 2001 From: Cheick Keita <chkeita@microsoft.com> Date: Mon, 30 Oct 2023 18:39:06 -0700 Subject: [PATCH 3/3] fix test --- src/ApiService/ApiService/onefuzzlib/JobCrashReported.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ApiService/ApiService/onefuzzlib/JobCrashReported.cs b/src/ApiService/ApiService/onefuzzlib/JobCrashReported.cs index e0b903979b..2739c5945e 100644 --- a/src/ApiService/ApiService/onefuzzlib/JobCrashReported.cs +++ b/src/ApiService/ApiService/onefuzzlib/JobCrashReported.cs @@ -13,7 +13,7 @@ public JobCrashReportedOperations(ILogger<JobCrashReportedOperations> logTracer, } public async Task<bool> CrashReported(Guid jobId) { - return await QueryAsync(Query.RowKey(jobId.ToString())).AnyAsync(); + return await QueryAsync(Query.PartitionKey(jobId.ToString())).AnyAsync(); } public async Task<OneFuzzResultVoid> ReportCrash(Guid jobId, Guid taskId) {