mirror of
https://github.com/microsoft/onefuzz.git
synced 2025-06-18 20:58:06 +00:00
Implement new_files
(#1794)
* Checkpoint * Checkpoint * More merge resolving * Code complete * Tested that it works * Keep the queue name different for now * Query was wrong, should be and * Style * Fix compile issue * Change report to use string instead of SHA, fixes tests as well * PR comments * Comments and formatting
This commit is contained in:
@ -25,6 +25,7 @@
|
||||
<PackageReference Include="Azure.ResourceManager.Resources" Version="1.0.0" />
|
||||
<PackageReference Include="Azure.ResourceManager.Storage" Version="1.0.0-beta.8" />
|
||||
<PackageReference Include="Azure.Storage.Queues" Version="12.9.0" />
|
||||
<PackageReference Include="Azure.Storage.Blobs" Version="12.11.0" />
|
||||
<PackageReference Include="Microsoft.Graph" Version="4.24.0" />
|
||||
<PackageReference Include="Microsoft.Identity.Client" Version="4.43.0" />
|
||||
<PackageReference Include="Microsoft.Identity.Web.TokenCache" Version="1.23.1" />
|
||||
|
@ -1,5 +1,4 @@
|
||||
using System;
|
||||
namespace Microsoft.OneFuzz.Service;
|
||||
namespace Microsoft.OneFuzz.Service;
|
||||
|
||||
public enum LogDestination
|
||||
{
|
||||
|
@ -1,6 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using System.Net.Http.Headers;
|
||||
|
||||
|
@ -1,6 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.ApplicationInsights;
|
||||
using Microsoft.ApplicationInsights;
|
||||
using Microsoft.ApplicationInsights.Extensibility;
|
||||
using Microsoft.ApplicationInsights.DataContracts;
|
||||
|
||||
|
@ -178,6 +178,24 @@ public static class ScalesetStateHelper
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public static class TaskStateHelper
|
||||
{
|
||||
static ConcurrentDictionary<string, TaskState[]> _states = new ConcurrentDictionary<string, TaskState[]>();
|
||||
public static TaskState[] Available()
|
||||
{
|
||||
return
|
||||
_states.GetOrAdd("Available", k =>
|
||||
{
|
||||
return
|
||||
new[]{
|
||||
TaskState.Waiting,
|
||||
TaskState.Scheduled,
|
||||
TaskState.SettingUp,
|
||||
TaskState.Running,
|
||||
TaskState.WaitJob
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
@ -1,5 +1,4 @@
|
||||
using Microsoft.OneFuzz.Service.OneFuzzLib.Orm;
|
||||
using System;
|
||||
using Microsoft.OneFuzz.Service.OneFuzzLib.Orm;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using PoolName = System.String;
|
||||
@ -37,7 +36,7 @@ public enum EventType
|
||||
FileAdded,
|
||||
TaskHeartbeat,
|
||||
NodeHeartbeat,
|
||||
InstanceConfigUpdated
|
||||
InstanceConfigUpdated,
|
||||
}
|
||||
|
||||
public abstract record BaseEvent()
|
||||
@ -50,6 +49,9 @@ public abstract record BaseEvent()
|
||||
EventNodeHeartbeat _ => EventType.NodeHeartbeat,
|
||||
EventTaskHeartbeat _ => EventType.TaskHeartbeat,
|
||||
EventInstanceConfigUpdated _ => EventType.InstanceConfigUpdated,
|
||||
EventCrashReported _ => EventType.CrashReported,
|
||||
EventRegressionReported _ => EventType.RegressionReported,
|
||||
EventFileAdded _ => EventType.FileAdded,
|
||||
_ => throw new NotImplementedException(),
|
||||
};
|
||||
|
||||
@ -62,6 +64,9 @@ public abstract record BaseEvent()
|
||||
EventType.NodeHeartbeat => typeof(EventNodeHeartbeat),
|
||||
EventType.InstanceConfigUpdated => typeof(EventInstanceConfigUpdated),
|
||||
EventType.TaskHeartbeat => typeof(EventTaskHeartbeat),
|
||||
EventType.CrashReported => typeof(EventCrashReported),
|
||||
EventType.RegressionReported => typeof(EventRegressionReported),
|
||||
EventType.FileAdded => typeof(EventFileAdded),
|
||||
_ => throw new ArgumentException($"invalid input {eventType}"),
|
||||
|
||||
};
|
||||
@ -249,25 +254,25 @@ public record EventNodeHeartbeat(
|
||||
// NodeState state
|
||||
// ) : BaseEvent();
|
||||
|
||||
// record EventCrashReported(
|
||||
// Report Report,
|
||||
// Container Container,
|
||||
// [property: JsonPropertyName("filename")] String FileName,
|
||||
// TaskConfig? TaskConfig
|
||||
// ) : BaseEvent();
|
||||
record EventCrashReported(
|
||||
Report Report,
|
||||
Container Container,
|
||||
[property: JsonPropertyName("filename")] String FileName,
|
||||
TaskConfig? TaskConfig
|
||||
) : BaseEvent();
|
||||
|
||||
// record EventRegressionReported(
|
||||
// RegressionReport RegressionReport,
|
||||
// Container Container,
|
||||
// [property: JsonPropertyName("filename")] String FileName,
|
||||
// TaskConfig? TaskConfig
|
||||
// ) : BaseEvent();
|
||||
record EventRegressionReported(
|
||||
RegressionReport RegressionReport,
|
||||
Container Container,
|
||||
[property: JsonPropertyName("filename")] String FileName,
|
||||
TaskConfig? TaskConfig
|
||||
) : BaseEvent();
|
||||
|
||||
|
||||
// record EventFileAdded(
|
||||
// Container Container,
|
||||
// [property: JsonPropertyName("filename")] String FileName
|
||||
// ) : BaseEvent();
|
||||
record EventFileAdded(
|
||||
Container Container,
|
||||
[property: JsonPropertyName("filename")] String FileName
|
||||
) : BaseEvent();
|
||||
|
||||
|
||||
public record EventInstanceConfigUpdated(
|
||||
|
@ -1,9 +1,5 @@
|
||||
using Microsoft.OneFuzz.Service.OneFuzzLib.Orm;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
using Container = System.String;
|
||||
using Region = System.String;
|
||||
using PoolName = System.String;
|
||||
using Endpoint = System.String;
|
||||
@ -397,32 +393,74 @@ public record Scaleset(
|
||||
|
||||
) : EntityBase();
|
||||
|
||||
public record Container(string ContainerName)
|
||||
{
|
||||
public string ContainerName { get; } = ContainerName.All(c => char.IsLetterOrDigit(c) || c == '-') ? ContainerName : throw new ArgumentException("Container name must have only numbers, letters or dashes");
|
||||
}
|
||||
|
||||
public record Notification(
|
||||
DateTime? Timestamp,
|
||||
Container Container,
|
||||
Guid NotificationId,
|
||||
NotificationTemplate Config
|
||||
) : EntityBase();
|
||||
|
||||
public record BlobRef(
|
||||
string Account,
|
||||
Container Container,
|
||||
string Name
|
||||
Container container,
|
||||
string name
|
||||
);
|
||||
|
||||
|
||||
public record Report(
|
||||
string? InputURL,
|
||||
string? InputUrl,
|
||||
BlobRef? InputBlob,
|
||||
string? Executable,
|
||||
string Executable,
|
||||
string CrashType,
|
||||
string CrashSite,
|
||||
List<string> CallStack,
|
||||
string CallStackSha256,
|
||||
string InputSha256,
|
||||
string? AsanLog,
|
||||
Guid TaskID,
|
||||
Guid JobID,
|
||||
Guid TaskId,
|
||||
Guid JobId,
|
||||
int? ScarinessScore,
|
||||
string? ScarinessDescription,
|
||||
List<string> MinimizedStack,
|
||||
List<string>? MinimizedStack,
|
||||
string? MinimizedStackSha256,
|
||||
List<string> MinimizedStackFunctionNames,
|
||||
List<string>? MinimizedStackFunctionNames,
|
||||
string? MinimizedStackFunctionNamesSha256,
|
||||
List<string> MinimizedStackFunctionLines,
|
||||
List<string>? MinimizedStackFunctionLines,
|
||||
string? MinimizedStackFunctionLinesSha256
|
||||
);
|
||||
|
||||
public record NoReproReport(
|
||||
string InputSha,
|
||||
BlobRef? InputBlob,
|
||||
string? Executable,
|
||||
Guid TaskId,
|
||||
Guid JobId,
|
||||
int Tries,
|
||||
string? Error
|
||||
);
|
||||
|
||||
public record CrashTestResult(
|
||||
Report? CrashReport,
|
||||
NoReproReport? NoReproReport
|
||||
);
|
||||
|
||||
public record RegressionReport(
|
||||
CrashTestResult CrashTestResult,
|
||||
CrashTestResult? OriginalCrashTestResult
|
||||
);
|
||||
|
||||
public record NotificationTemplate(
|
||||
AdoTemplate? AdoTemplate,
|
||||
TeamsTemplate? TeamsTemplate,
|
||||
GithubIssuesTemplate? GithubIssuesTemplate
|
||||
);
|
||||
|
||||
public record AdoTemplate();
|
||||
|
||||
public record TeamsTemplate();
|
||||
|
||||
public record GithubIssuesTemplate();
|
||||
|
@ -1,6 +1,4 @@
|
||||
using Microsoft.OneFuzz.Service.OneFuzzLib.Orm;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Microsoft.OneFuzz.Service;
|
||||
|
@ -1,8 +1,10 @@
|
||||
// to avoid collision with Task in model.cs
|
||||
global using Async = System.Threading.Tasks;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
global using System;
|
||||
global using System.Collections.Generic;
|
||||
global using System.Linq;
|
||||
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Azure.Functions.Worker.Middleware;
|
||||
@ -76,6 +78,9 @@ public class Program
|
||||
.AddScoped<IProxyOperations, ProxyOperations>()
|
||||
.AddScoped<IConfigOperations, ConfigOperations>()
|
||||
.AddScoped<IScalesetOperations, ScalesetOperations>()
|
||||
.AddScoped<IContainers, Containers>()
|
||||
.AddScoped<IReports, Reports>()
|
||||
.AddScoped<INotificationOperations, NotificationOperations>()
|
||||
|
||||
//TODO: move out expensive resources into separate class, and add those as Singleton
|
||||
// ArmClient, Table Client(s), Queue Client(s), HttpClient, etc.
|
||||
|
@ -1,9 +1,6 @@
|
||||
using System;
|
||||
using Microsoft.Azure.Functions.Worker;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json;
|
||||
using Microsoft.OneFuzz.Service.OneFuzzLib.Orm;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.OneFuzz.Service;
|
||||
|
||||
@ -17,51 +14,53 @@ public class QueueFileChanges
|
||||
|
||||
private readonly IStorage _storage;
|
||||
|
||||
public QueueFileChanges(ILogTracer log, IStorage storage)
|
||||
private readonly INotificationOperations _notificationOperations;
|
||||
|
||||
public QueueFileChanges(ILogTracer log, IStorage storage, INotificationOperations notificationOperations)
|
||||
{
|
||||
_log = log;
|
||||
_storage = storage;
|
||||
_notificationOperations = notificationOperations;
|
||||
}
|
||||
|
||||
[Function("QueueFileChanges")]
|
||||
public Async.Task Run(
|
||||
public async Async.Task Run(
|
||||
[QueueTrigger("file-changes-refactored", Connection = "AzureWebJobsStorage")] string msg,
|
||||
int dequeueCount)
|
||||
{
|
||||
var fileChangeEvent = JsonSerializer.Deserialize<Dictionary<string, string>>(msg, EntityConverter.GetJsonSerializerOptions());
|
||||
var fileChangeEvent = JsonSerializer.Deserialize<JsonDocument>(msg, EntityConverter.GetJsonSerializerOptions());
|
||||
var lastTry = dequeueCount == MAX_DEQUEUE_COUNT;
|
||||
|
||||
var _ = fileChangeEvent ?? throw new ArgumentException("Unable to parse queue trigger as JSON");
|
||||
|
||||
// check type first before calling Azure APIs
|
||||
const string eventType = "eventType";
|
||||
if (!fileChangeEvent.ContainsKey(eventType)
|
||||
|| fileChangeEvent[eventType] != "Microsoft.Storage.BlobCreated")
|
||||
if (!fileChangeEvent.RootElement.TryGetProperty(eventType, out var eventTypeElement)
|
||||
|| eventTypeElement.GetString() != "Microsoft.Storage.BlobCreated")
|
||||
{
|
||||
return Async.Task.CompletedTask;
|
||||
return;
|
||||
}
|
||||
|
||||
const string topic = "topic";
|
||||
if (!fileChangeEvent.ContainsKey(topic)
|
||||
|| !_storage.CorpusAccounts().Contains(fileChangeEvent[topic]))
|
||||
if (!fileChangeEvent.RootElement.TryGetProperty(topic, out var topicElement)
|
||||
|| !_storage.CorpusAccounts().Contains(topicElement.GetString()))
|
||||
{
|
||||
return Async.Task.CompletedTask;
|
||||
return;
|
||||
}
|
||||
|
||||
file_added(_log, fileChangeEvent, lastTry);
|
||||
return Async.Task.CompletedTask;
|
||||
await file_added(_log, fileChangeEvent, lastTry);
|
||||
}
|
||||
|
||||
private void file_added(ILogTracer log, Dictionary<string, string> fileChangeEvent, bool failTaskOnTransientError)
|
||||
private async Async.Task file_added(ILogTracer log, JsonDocument fileChangeEvent, bool failTaskOnTransientError)
|
||||
{
|
||||
var data = JsonSerializer.Deserialize<Dictionary<string, string>>(fileChangeEvent["data"])!;
|
||||
var url = data["url"];
|
||||
var data = fileChangeEvent.RootElement.GetProperty("data");
|
||||
var url = data.GetProperty("url").GetString()!;
|
||||
var parts = url.Split("/").Skip(3).ToList();
|
||||
|
||||
var container = parts[0];
|
||||
var path = string.Join('/', parts.Skip(1));
|
||||
|
||||
log.Info($"file added container: {container} - path: {path}");
|
||||
// TODO: new_files(container, path, fail_task_on_transient_error)
|
||||
await _notificationOperations.NewFiles(new Container(container), path, failTaskOnTransientError);
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
using System;
|
||||
using Microsoft.Azure.Functions.Worker;
|
||||
using System.Text.Json;
|
||||
using Microsoft.OneFuzz.Service.OneFuzzLib.Orm;
|
||||
|
@ -1,4 +1,3 @@
|
||||
using System;
|
||||
using Microsoft.Azure.Functions.Worker;
|
||||
using System.Text.Json;
|
||||
using Microsoft.OneFuzz.Service.OneFuzzLib.Orm;
|
||||
|
@ -1,4 +1,3 @@
|
||||
using System;
|
||||
using Microsoft.Azure.Functions.Worker;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Text.Json;
|
||||
|
@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.Functions.Worker;
|
||||
using Microsoft.Azure.Functions.Worker.Http;
|
||||
|
@ -1,6 +1,4 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.Functions.Worker.Http;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
|
93
src/ApiService/ApiService/onefuzzlib/Containers.cs
Normal file
93
src/ApiService/ApiService/onefuzzlib/Containers.cs
Normal file
@ -0,0 +1,93 @@
|
||||
using System.Threading.Tasks;
|
||||
using Azure.ResourceManager;
|
||||
using Azure.Storage.Blobs;
|
||||
using Azure.Storage;
|
||||
using Azure;
|
||||
|
||||
namespace Microsoft.OneFuzz.Service;
|
||||
|
||||
public interface IContainers
|
||||
{
|
||||
public Task<IEnumerable<byte>?> GetBlob(Container container, string name, StorageType storageType);
|
||||
|
||||
public Async.Task<BlobContainerClient?> FindContainer(Container container, StorageType storageType);
|
||||
|
||||
public Uri GetFileSasUrl(Container container, string name, StorageType storageType, bool read = false, bool add = false, bool create = false, bool write = false, bool delete = false, bool delete_previous_version = false, bool tag = false, int days = 30, int hours = 0, int minutes = 0);
|
||||
|
||||
}
|
||||
|
||||
public class Containers : IContainers
|
||||
{
|
||||
private ILogTracer _log;
|
||||
private IStorage _storage;
|
||||
private ICreds _creds;
|
||||
private ArmClient _armClient;
|
||||
public Containers(ILogTracer log, IStorage storage, ICreds creds)
|
||||
{
|
||||
_log = log;
|
||||
_storage = storage;
|
||||
_creds = creds;
|
||||
_armClient = new ArmClient(credential: _creds.GetIdentity(), defaultSubscriptionId: _creds.GetSubcription());
|
||||
}
|
||||
public async Task<IEnumerable<byte>?> GetBlob(Container container, string name, StorageType storageType)
|
||||
{
|
||||
var client = await FindContainer(container, storageType);
|
||||
|
||||
if (client == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return (await client.GetBlobClient(name).DownloadContentAsync())
|
||||
.Value.Content.ToArray();
|
||||
}
|
||||
catch (RequestFailedException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Async.Task<BlobContainerClient?> FindContainer(Container container, StorageType storageType)
|
||||
{
|
||||
// # check secondary accounts first by searching in reverse.
|
||||
// #
|
||||
// # By implementation, the primary account is specified first, followed by
|
||||
// # any secondary accounts.
|
||||
// #
|
||||
// # Secondary accounts, if they exist, are preferred for containers and have
|
||||
// # increased IOP rates, this should be a slight optimization
|
||||
return await _storage.GetAccounts(storageType)
|
||||
.Reverse()
|
||||
.Select(account => GetBlobService(account)?.GetBlobContainerClient(container.ContainerName))
|
||||
.ToAsyncEnumerable()
|
||||
.WhereAwait(async client => client != null && (await client.ExistsAsync()).Value)
|
||||
.FirstOrDefaultAsync();
|
||||
}
|
||||
|
||||
private BlobServiceClient? GetBlobService(string accountId)
|
||||
{
|
||||
_log.Info($"getting blob container (account_id: {accountId}");
|
||||
var (accountName, accountKey) = _storage.GetStorageAccountNameAndKey(accountId);
|
||||
if (accountName == null)
|
||||
{
|
||||
_log.Error("Failed to get storage account name");
|
||||
return null;
|
||||
}
|
||||
var storageKeyCredential = new StorageSharedKeyCredential(accountName, accountKey);
|
||||
var accountUrl = GetUrl(accountName);
|
||||
return new BlobServiceClient(accountUrl, storageKeyCredential);
|
||||
}
|
||||
|
||||
private static Uri GetUrl(string accountName)
|
||||
{
|
||||
return new Uri($"https://{accountName}.blob.core.windows.net/");
|
||||
}
|
||||
|
||||
public Uri GetFileSasUrl(Container container, string name, StorageType storageType, bool read = false, bool add = false, bool create = false, bool write = false, bool delete = false, bool delete_previous_version = false, bool tag = false, int days = 30, int hours = 0, int minutes = 0)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,4 @@
|
||||
using Microsoft.OneFuzz.Service.OneFuzzLib.Orm;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
@ -1,5 +1,4 @@
|
||||
using ApiService.OneFuzzLib.Orm;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.OneFuzz.Service;
|
||||
|
@ -1,6 +1,4 @@
|
||||
using ApiService.OneFuzzLib.Orm;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.OneFuzz.Service;
|
||||
|
146
src/ApiService/ApiService/onefuzzlib/NotificationOperations.cs
Normal file
146
src/ApiService/ApiService/onefuzzlib/NotificationOperations.cs
Normal file
@ -0,0 +1,146 @@
|
||||
using System.Text.Json;
|
||||
using ApiService.OneFuzzLib.Orm;
|
||||
|
||||
namespace Microsoft.OneFuzz.Service;
|
||||
|
||||
public interface INotificationOperations
|
||||
{
|
||||
Async.Task NewFiles(Container container, string filename, bool failTaskOnTransientError);
|
||||
}
|
||||
|
||||
public class NotificationOperations : Orm<Notification>, INotificationOperations
|
||||
{
|
||||
private ILogTracer _log;
|
||||
private IReports _reports;
|
||||
private ITaskOperations _taskOperations;
|
||||
|
||||
private IContainers _containers;
|
||||
|
||||
private IQueue _queue;
|
||||
|
||||
private IEvents _events;
|
||||
|
||||
public NotificationOperations(ILogTracer log, IStorage storage, IReports reports, ITaskOperations taskOperations, IContainers containers, IQueue queue, IEvents events)
|
||||
: base(storage)
|
||||
{
|
||||
_log = log;
|
||||
_reports = reports;
|
||||
_taskOperations = taskOperations;
|
||||
_containers = containers;
|
||||
_queue = queue;
|
||||
_events = events;
|
||||
}
|
||||
public async Async.Task NewFiles(Container container, string filename, bool failTaskOnTransientError)
|
||||
{
|
||||
var notifications = GetNotifications(container);
|
||||
var hasNotifications = await notifications.AnyAsync();
|
||||
var report = await _reports.GetReportOrRegression(container, filename, expectReports: hasNotifications);
|
||||
|
||||
if (!hasNotifications)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var done = new List<NotificationTemplate>();
|
||||
await foreach (var notification in notifications)
|
||||
{
|
||||
if (done.Contains(notification.Config))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
done.Add(notification.Config);
|
||||
|
||||
if (notification.Config.TeamsTemplate != null)
|
||||
{
|
||||
NotifyTeams(notification.Config.TeamsTemplate, container, filename, report);
|
||||
}
|
||||
|
||||
if (report == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (notification.Config.AdoTemplate != null)
|
||||
{
|
||||
NotifyAdo(notification.Config.AdoTemplate, container, filename, report, failTaskOnTransientError);
|
||||
}
|
||||
|
||||
if (notification.Config.GithubIssuesTemplate != null)
|
||||
{
|
||||
GithubIssue(notification.Config.GithubIssuesTemplate, container, filename, report);
|
||||
}
|
||||
}
|
||||
|
||||
await foreach (var (task, containers) in GetQueueTasks())
|
||||
{
|
||||
if (containers.Contains(container.ContainerName))
|
||||
{
|
||||
_log.Info($"queuing input {container.ContainerName} {filename} {task.TaskId}");
|
||||
var url = _containers.GetFileSasUrl(container, filename, StorageType.Corpus, read: true, delete: true);
|
||||
await _queue.SendMessage(task.TaskId.ToString(), System.Text.Encoding.UTF8.GetBytes(url.ToString()), StorageType.Corpus);
|
||||
}
|
||||
}
|
||||
|
||||
if (report == null)
|
||||
{
|
||||
await _events.SendEvent(new EventFileAdded(container, filename));
|
||||
}
|
||||
else if (report.Report != null)
|
||||
{
|
||||
var reportTask = await _taskOperations.GetByJobIdAndTaskId(report.Report.JobId, report.Report.TaskId);
|
||||
|
||||
var crashReportedEvent = new EventCrashReported(report.Report, container, filename, reportTask?.Config);
|
||||
await _events.SendEvent(crashReportedEvent);
|
||||
}
|
||||
else if (report.RegressionReport != null)
|
||||
{
|
||||
var reportTask = await GetRegressionReportTask(report.RegressionReport);
|
||||
|
||||
var regressionEvent = new EventRegressionReported(report.RegressionReport, container, filename, reportTask?.Config);
|
||||
}
|
||||
}
|
||||
|
||||
public IAsyncEnumerable<Notification> GetNotifications(Container container)
|
||||
{
|
||||
return QueryAsync(filter: $"container eq '{container.ContainerName}'");
|
||||
}
|
||||
|
||||
public IAsyncEnumerable<(Task, IEnumerable<string>)> GetQueueTasks()
|
||||
{
|
||||
// Nullability mismatch: We filter tuples where the containers are null
|
||||
return _taskOperations.SearchStates(states: TaskStateHelper.Available())
|
||||
.Select(task => (task, _taskOperations.GetInputContainerQueues(task.Config)))
|
||||
.Where(taskTuple => taskTuple.Item2 != null)!;
|
||||
}
|
||||
|
||||
private async Async.Task<Task?> GetRegressionReportTask(RegressionReport report)
|
||||
{
|
||||
if (report.CrashTestResult.CrashReport != null)
|
||||
{
|
||||
return await _taskOperations.GetByJobIdAndTaskId(report.CrashTestResult.CrashReport.JobId, report.CrashTestResult.CrashReport.TaskId);
|
||||
}
|
||||
if (report.CrashTestResult.NoReproReport != null)
|
||||
{
|
||||
return await _taskOperations.GetByJobIdAndTaskId(report.CrashTestResult.NoReproReport.JobId, report.CrashTestResult.NoReproReport.TaskId);
|
||||
}
|
||||
|
||||
_log.Error($"unable to find crash_report or no repro entry for report: {JsonSerializer.Serialize(report)}");
|
||||
return null;
|
||||
}
|
||||
|
||||
private void GithubIssue(GithubIssuesTemplate config, Container container, string filename, RegressionReportOrReport? report)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private void NotifyAdo(AdoTemplate config, Container container, string filename, RegressionReportOrReport report, bool failTaskOnTransientError)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private void NotifyTeams(TeamsTemplate config, Container container, string filename, RegressionReportOrReport? report)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
@ -1,6 +1,4 @@
|
||||
using ApiService.OneFuzzLib.Orm;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.OneFuzz.Service;
|
||||
|
@ -1,7 +1,6 @@
|
||||
using Azure.Storage;
|
||||
using Azure.Storage.Queues;
|
||||
using Microsoft.OneFuzz.Service.OneFuzzLib.Orm;
|
||||
using System;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
98
src/ApiService/ApiService/onefuzzlib/Reports.cs
Normal file
98
src/ApiService/ApiService/onefuzzlib/Reports.cs
Normal file
@ -0,0 +1,98 @@
|
||||
using System.Text.Json;
|
||||
using Microsoft.OneFuzz.Service.OneFuzzLib.Orm;
|
||||
|
||||
namespace Microsoft.OneFuzz.Service;
|
||||
|
||||
public interface IReports
|
||||
{
|
||||
public Async.Task<RegressionReportOrReport?> GetReportOrRegression(Container container, string fileName, bool expectReports = false, params string[] args);
|
||||
}
|
||||
|
||||
public class Reports : IReports
|
||||
{
|
||||
private ILogTracer _log;
|
||||
private IContainers _containers;
|
||||
public Reports(ILogTracer log, IContainers containers)
|
||||
{
|
||||
_log = log;
|
||||
_containers = containers;
|
||||
}
|
||||
|
||||
public async Async.Task<RegressionReportOrReport?> GetReportOrRegression(Container container, string fileName, bool expectReports = false, params string[] args)
|
||||
{
|
||||
var filePath = String.Join("/", new[] { container.ContainerName, fileName });
|
||||
if (!fileName.EndsWith(".json"))
|
||||
{
|
||||
if (expectReports)
|
||||
{
|
||||
_log.Error($"get_report invalid extension: {filePath}");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
var blob = await _containers.GetBlob(container, fileName, StorageType.Corpus);
|
||||
|
||||
if (blob == null)
|
||||
{
|
||||
if (expectReports)
|
||||
{
|
||||
_log.Error($"get_report invalid blob: {filePath}");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
return ParseReportOrRegression(blob, filePath, expectReports);
|
||||
}
|
||||
|
||||
private RegressionReportOrReport? ParseReportOrRegression(string content, string? filePath, bool expectReports = false)
|
||||
{
|
||||
try
|
||||
{
|
||||
return new RegressionReportOrReport
|
||||
{
|
||||
RegressionReport = JsonSerializer.Deserialize<RegressionReport>(content, EntityConverter.GetJsonSerializerOptions())
|
||||
};
|
||||
}
|
||||
catch (JsonException e)
|
||||
{
|
||||
try
|
||||
{
|
||||
return new RegressionReportOrReport
|
||||
{
|
||||
Report = JsonSerializer.Deserialize<Report>(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}");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private RegressionReportOrReport? ParseReportOrRegression(IEnumerable<byte> content, string? filePath, bool expectReports = false)
|
||||
{
|
||||
try
|
||||
{
|
||||
var str = System.Text.Encoding.UTF8.GetString(content.ToArray());
|
||||
return ParseReportOrRegression(str, filePath, expectReports);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (expectReports)
|
||||
{
|
||||
_log.Error($"unable to parse report ({filePath}): unicode decode of report failed - {e.Message} {e.StackTrace}");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class RegressionReportOrReport
|
||||
{
|
||||
public RegressionReport? RegressionReport { get; set; }
|
||||
public Report? Report { get; set; }
|
||||
}
|
@ -1,5 +1,4 @@
|
||||
using ApiService.OneFuzzLib.Orm;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.OneFuzz.Service;
|
||||
|
||||
|
@ -1,10 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using Azure.ResourceManager;
|
||||
using Azure.ResourceManager.Storage;
|
||||
using Azure.Core;
|
||||
using System.Text.Json;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.OneFuzz.Service;
|
||||
|
||||
@ -21,6 +18,8 @@ public interface IStorage
|
||||
public IEnumerable<string> CorpusAccounts();
|
||||
string GetPrimaryAccount(StorageType storageType);
|
||||
public (string?, string?) GetStorageAccountNameAndKey(string accountId);
|
||||
|
||||
public IEnumerable<string> GetAccounts(StorageType storageType);
|
||||
}
|
||||
|
||||
public class Storage : IStorage
|
||||
@ -114,4 +113,17 @@ public class Storage : IStorage
|
||||
var key = storageAccount.GetKeys().Value.Keys.FirstOrDefault();
|
||||
return (resourceId.Name, key?.Value);
|
||||
}
|
||||
|
||||
public IEnumerable<string> GetAccounts(StorageType storageType)
|
||||
{
|
||||
switch (storageType)
|
||||
{
|
||||
case StorageType.Corpus:
|
||||
return CorpusAccounts();
|
||||
case StorageType.Config:
|
||||
return new[] { GetFuncStorage() };
|
||||
default:
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,18 @@
|
||||
using ApiService.OneFuzzLib.Orm;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.OneFuzz.Service;
|
||||
|
||||
public interface ITaskOperations : IOrm<Task>
|
||||
{
|
||||
Async.Task<Task?> GetByTaskId(Guid taskId);
|
||||
|
||||
Async.Task<Task?> GetByJobIdAndTaskId(Guid jobId, Guid taskId);
|
||||
|
||||
|
||||
IAsyncEnumerable<Task> SearchStates(Guid? jobId = null, IEnumerable<TaskState>? states = null);
|
||||
|
||||
IEnumerable<string>? GetInputContainerQueues(TaskConfig config);
|
||||
|
||||
}
|
||||
|
||||
public class TaskOperations : Orm<Task>, ITaskOperations
|
||||
@ -25,4 +31,37 @@ public class TaskOperations : Orm<Task>, ITaskOperations
|
||||
return await data.FirstOrDefaultAsync();
|
||||
}
|
||||
|
||||
public async Async.Task<Task?> GetByJobIdAndTaskId(Guid jobId, Guid taskId)
|
||||
{
|
||||
var data = QueryAsync(filter: $"PartitionKey eq '{jobId}' and RowKey eq '{taskId}'");
|
||||
|
||||
return await data.FirstOrDefaultAsync();
|
||||
}
|
||||
public IAsyncEnumerable<Task> SearchStates(Guid? jobId = null, IEnumerable<TaskState>? states = null)
|
||||
{
|
||||
var queryString = String.Empty;
|
||||
if (jobId != null)
|
||||
{
|
||||
queryString += $"PartitionKey eq '{jobId}'";
|
||||
}
|
||||
|
||||
if (states != null)
|
||||
{
|
||||
if (jobId != null)
|
||||
{
|
||||
queryString += " and ";
|
||||
}
|
||||
|
||||
var statesString = string.Join(",", states);
|
||||
queryString += $"state in ({statesString})";
|
||||
}
|
||||
|
||||
return QueryAsync(filter: queryString);
|
||||
}
|
||||
|
||||
public IEnumerable<string>? GetInputContainerQueues(TaskConfig config)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,6 +1,3 @@
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.OneFuzz.Service;
|
||||
|
||||
public static class ObjectExtention
|
||||
|
@ -1,6 +1,4 @@
|
||||
using ApiService.OneFuzzLib.Orm;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.OneFuzz.Service;
|
||||
|
||||
|
@ -1,8 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.OneFuzz.Service.OneFuzzLib.Orm;
|
||||
namespace Microsoft.OneFuzz.Service.OneFuzzLib.Orm;
|
||||
|
||||
public class CaseConverter
|
||||
{
|
||||
|
@ -1,7 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text.Encodings.Web;
|
||||
|
@ -1,13 +1,10 @@
|
||||
using Azure.Data.Tables;
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Collections.Concurrent;
|
||||
using Azure;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.OneFuzz.Service.OneFuzzLib.Orm;
|
||||
|
||||
|
@ -1,9 +1,6 @@
|
||||
using Azure.Data.Tables;
|
||||
using Microsoft.OneFuzz.Service;
|
||||
using Microsoft.OneFuzz.Service.OneFuzzLib.Orm;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ApiService.OneFuzzLib.Orm
|
||||
|
@ -107,6 +107,16 @@
|
||||
"System.Text.Json": "4.7.2"
|
||||
}
|
||||
},
|
||||
"Azure.Storage.Blobs": {
|
||||
"type": "Direct",
|
||||
"requested": "[12.11.0, )",
|
||||
"resolved": "12.11.0",
|
||||
"contentHash": "50eRjIhY7Q1JN7kT2MSawDKCcwSb7uRZUkz00P/BLjSg47gm2hxUYsnJPyvzCHntYMbOWzrvaVQTwYwXabaR5Q==",
|
||||
"dependencies": {
|
||||
"Azure.Storage.Common": "12.10.0",
|
||||
"System.Text.Json": "4.7.2"
|
||||
}
|
||||
},
|
||||
"Azure.Storage.Queues": {
|
||||
"type": "Direct",
|
||||
"requested": "[12.9.0, )",
|
||||
|
@ -249,6 +249,41 @@ namespace Tests
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
public static Gen<Report> Report()
|
||||
{
|
||||
return Arb.Generate<Tuple<string, BlobRef, List<string>, Guid, int>>().Select(
|
||||
arg =>
|
||||
new Report(
|
||||
InputUrl: arg.Item1,
|
||||
InputBlob: arg.Item2,
|
||||
Executable: arg.Item1,
|
||||
CrashType: arg.Item1,
|
||||
CrashSite: arg.Item1,
|
||||
CallStack: arg.Item3,
|
||||
CallStackSha256: arg.Item1,
|
||||
InputSha256: arg.Item1,
|
||||
AsanLog: arg.Item1,
|
||||
TaskId: arg.Item4,
|
||||
JobId: arg.Item4,
|
||||
ScarinessScore: arg.Item5,
|
||||
ScarinessDescription: arg.Item1,
|
||||
MinimizedStack: arg.Item3,
|
||||
MinimizedStackSha256: arg.Item1,
|
||||
MinimizedStackFunctionNames: arg.Item3,
|
||||
MinimizedStackFunctionNamesSha256: arg.Item1,
|
||||
MinimizedStackFunctionLines: arg.Item3,
|
||||
MinimizedStackFunctionLinesSha256: arg.Item1
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public static Gen<Container> Container()
|
||||
{
|
||||
return Arb.Generate<Tuple<NonNull<string>>>().Select(
|
||||
arg => new Container(string.Join("", arg.Item1.Get.Where(c => char.IsLetterOrDigit(c) || c == '-'))!)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public class OrmArb
|
||||
@ -327,6 +362,16 @@ namespace Tests
|
||||
{
|
||||
return Arb.From(OrmGenerators.WebhookMessage());
|
||||
}
|
||||
|
||||
public static Arbitrary<Report> Report()
|
||||
{
|
||||
return Arb.From(OrmGenerators.Report());
|
||||
}
|
||||
|
||||
public static Arbitrary<Container> Container()
|
||||
{
|
||||
return Arb.From(OrmGenerators.Container());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user