mirror of
https://github.com/microsoft/onefuzz.git
synced 2025-06-16 03:48:09 +00:00
Laying groundwork for TimerRepro implementation (#2168)
* Add some pre-reqs for TimerRepro * Format and add some API to creds * PR comment
This commit is contained in:
@ -165,7 +165,7 @@ public static class ScalesetStateHelper {
|
|||||||
|
|
||||||
public static class VmStateHelper {
|
public static class VmStateHelper {
|
||||||
|
|
||||||
private static readonly IReadOnlySet<VmState> _needsWork = new HashSet<VmState> { VmState.Init, VmState.Init, VmState.ExtensionsLaunch, VmState.Stopping };
|
private static readonly IReadOnlySet<VmState> _needsWork = new HashSet<VmState> { VmState.Init, VmState.ExtensionsLaunch, VmState.Stopping };
|
||||||
private static readonly IReadOnlySet<VmState> _available = new HashSet<VmState> { VmState.Init, VmState.ExtensionsLaunch, VmState.ExtensionsFailed, VmState.VmAllocationFailed, VmState.Running, };
|
private static readonly IReadOnlySet<VmState> _available = new HashSet<VmState> { VmState.Init, VmState.ExtensionsLaunch, VmState.ExtensionsFailed, VmState.VmAllocationFailed, VmState.Running, };
|
||||||
|
|
||||||
public static IReadOnlySet<VmState> NeedsWork => _needsWork;
|
public static IReadOnlySet<VmState> NeedsWork => _needsWork;
|
||||||
|
@ -147,7 +147,12 @@ public record Proxy
|
|||||||
bool Outdated
|
bool Outdated
|
||||||
) : StatefulEntityBase<VmState>(State);
|
) : StatefulEntityBase<VmState>(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);
|
public record UserInfo(Guid? ApplicationId, Guid? ObjectId, String? Upn);
|
||||||
|
|
||||||
@ -413,7 +418,7 @@ public record Notification(
|
|||||||
public record BlobRef(
|
public record BlobRef(
|
||||||
string Account,
|
string Account,
|
||||||
Container container,
|
Container container,
|
||||||
string name
|
string Name
|
||||||
);
|
);
|
||||||
|
|
||||||
public record Report(
|
public record Report(
|
||||||
@ -436,7 +441,7 @@ public record Report(
|
|||||||
string? MinimizedStackFunctionNamesSha256,
|
string? MinimizedStackFunctionNamesSha256,
|
||||||
List<string>? MinimizedStackFunctionLines,
|
List<string>? MinimizedStackFunctionLines,
|
||||||
string? MinimizedStackFunctionLinesSha256
|
string? MinimizedStackFunctionLinesSha256
|
||||||
);
|
) : IReport;
|
||||||
|
|
||||||
public record NoReproReport(
|
public record NoReproReport(
|
||||||
string InputSha,
|
string InputSha,
|
||||||
@ -444,7 +449,7 @@ public record NoReproReport(
|
|||||||
string? Executable,
|
string? Executable,
|
||||||
Guid TaskId,
|
Guid TaskId,
|
||||||
Guid JobId,
|
Guid JobId,
|
||||||
int Tries,
|
long Tries,
|
||||||
string? Error
|
string? Error
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -456,7 +461,7 @@ public record CrashTestResult(
|
|||||||
public record RegressionReport(
|
public record RegressionReport(
|
||||||
CrashTestResult CrashTestResult,
|
CrashTestResult CrashTestResult,
|
||||||
CrashTestResult? OriginalCrashTestResult
|
CrashTestResult? OriginalCrashTestResult
|
||||||
);
|
) : IReport;
|
||||||
|
|
||||||
public record NotificationTemplate(
|
public record NotificationTemplate(
|
||||||
AdoTemplate? AdoTemplate,
|
AdoTemplate? AdoTemplate,
|
||||||
@ -471,8 +476,7 @@ public record TeamsTemplate();
|
|||||||
public record GithubIssuesTemplate();
|
public record GithubIssuesTemplate();
|
||||||
|
|
||||||
public record Repro(
|
public record Repro(
|
||||||
[PartitionKey] Guid VmId,
|
[PartitionKey][RowKey] Guid VmId,
|
||||||
[RowKey] Guid _,
|
|
||||||
Guid TaskId,
|
Guid TaskId,
|
||||||
ReproConfig Config,
|
ReproConfig Config,
|
||||||
VmState State,
|
VmState State,
|
||||||
|
@ -28,6 +28,10 @@ public interface ICreds {
|
|||||||
public Uri GetInstanceUrl();
|
public Uri GetInstanceUrl();
|
||||||
public Async.Task<Guid> GetScalesetPrincipalId();
|
public Async.Task<Guid> GetScalesetPrincipalId();
|
||||||
public Async.Task<T> QueryMicrosoftGraph<T>(HttpMethod method, string resource);
|
public Async.Task<T> QueryMicrosoftGraph<T>(HttpMethod method, string resource);
|
||||||
|
|
||||||
|
public GenericResource ParseResourceId(string resourceId);
|
||||||
|
|
||||||
|
public Async.Task<GenericResource> GetData(GenericResource resource);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Creds : ICreds {
|
public class Creds : ICreds {
|
||||||
@ -145,6 +149,17 @@ public class Creds : ICreds {
|
|||||||
throw new GraphQueryException($"request did not succeed: HTTP {response.StatusCode} - {errorText}");
|
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<GenericResource> GetData(GenericResource resource) {
|
||||||
|
if (!resource.HasData) {
|
||||||
|
return await resource.GetAsync();
|
||||||
|
}
|
||||||
|
return resource;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class GraphQueryException : Exception {
|
class GraphQueryException : Exception {
|
||||||
|
@ -19,7 +19,7 @@ public class NotificationOperations : Orm<Notification>, INotificationOperations
|
|||||||
public async Async.Task NewFiles(Container container, string filename, bool failTaskOnTransientError) {
|
public async Async.Task NewFiles(Container container, string filename, bool failTaskOnTransientError) {
|
||||||
var notifications = GetNotifications(container);
|
var notifications = GetNotifications(container);
|
||||||
var hasNotifications = await notifications.AnyAsync();
|
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) {
|
if (!hasNotifications) {
|
||||||
return;
|
return;
|
||||||
@ -34,19 +34,19 @@ public class NotificationOperations : Orm<Notification>, INotificationOperations
|
|||||||
done.Add(notification.Config);
|
done.Add(notification.Config);
|
||||||
|
|
||||||
if (notification.Config.TeamsTemplate != null) {
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (notification.Config.AdoTemplate != null) {
|
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) {
|
if (notification.Config.GithubIssuesTemplate != null) {
|
||||||
GithubIssue(notification.Config.GithubIssuesTemplate, container, filename, report);
|
GithubIssue(notification.Config.GithubIssuesTemplate, container, filename, reportOrRegression);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,15 +58,17 @@ public class NotificationOperations : Orm<Notification>, INotificationOperations
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (report?.Report != null) {
|
if (reportOrRegression is Report) {
|
||||||
var reportTask = await _context.TaskOperations.GetByJobIdAndTaskId(report.Report.JobId, report.Report.TaskId);
|
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);
|
await _context.Events.SendEvent(crashReportedEvent);
|
||||||
} else if (report?.RegressionReport != null) {
|
} else if (reportOrRegression is RegressionReport) {
|
||||||
var reportTask = await GetRegressionReportTask(report.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);
|
await _context.Events.SendEvent(regressionEvent);
|
||||||
} else {
|
} else {
|
||||||
await _context.Events.SendEvent(new EventFileAdded(container, filename));
|
await _context.Events.SendEvent(new EventFileAdded(container, filename));
|
||||||
@ -96,15 +98,15 @@ public class NotificationOperations : Orm<Notification>, INotificationOperations
|
|||||||
return null;
|
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();
|
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();
|
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();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ using Microsoft.OneFuzz.Service.OneFuzzLib.Orm;
|
|||||||
namespace Microsoft.OneFuzz.Service;
|
namespace Microsoft.OneFuzz.Service;
|
||||||
|
|
||||||
public interface IReports {
|
public interface IReports {
|
||||||
public Async.Task<RegressionReportOrReport?> GetReportOrRegression(Container container, string fileName, bool expectReports = false, params string[] args);
|
public Async.Task<IReport?> GetReportOrRegression(Container container, string fileName, bool expectReports = false, params string[] args);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Reports : IReports {
|
public class Reports : IReports {
|
||||||
@ -15,7 +15,7 @@ public class Reports : IReports {
|
|||||||
_containers = containers;
|
_containers = containers;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Async.Task<RegressionReportOrReport?> GetReportOrRegression(Container container, string fileName, bool expectReports = false, params string[] args) {
|
public async Async.Task<IReport?> GetReportOrRegression(Container container, string fileName, bool expectReports = false, params string[] args) {
|
||||||
var filePath = String.Join("/", new[] { container.ContainerName, fileName });
|
var filePath = String.Join("/", new[] { container.ContainerName, fileName });
|
||||||
if (!fileName.EndsWith(".json", StringComparison.Ordinal)) {
|
if (!fileName.EndsWith(".json", StringComparison.Ordinal)) {
|
||||||
if (expectReports) {
|
if (expectReports) {
|
||||||
@ -36,26 +36,20 @@ public class Reports : IReports {
|
|||||||
return ParseReportOrRegression(blob.ToString(), filePath, expectReports);
|
return ParseReportOrRegression(blob.ToString(), filePath, expectReports);
|
||||||
}
|
}
|
||||||
|
|
||||||
private RegressionReportOrReport? ParseReportOrRegression(string content, string? filePath, bool expectReports = false) {
|
private IReport? ParseReportOrRegression(string content, string? filePath, bool expectReports = false) {
|
||||||
try {
|
var regressionReport = JsonSerializer.Deserialize<RegressionReport>(content, EntityConverter.GetJsonSerializerOptions());
|
||||||
return new RegressionReportOrReport {
|
if (regressionReport == null || regressionReport.CrashTestResult == null) {
|
||||||
RegressionReport = JsonSerializer.Deserialize<RegressionReport>(content, EntityConverter.GetJsonSerializerOptions())
|
var report = JsonSerializer.Deserialize<Report>(content, EntityConverter.GetJsonSerializerOptions());
|
||||||
};
|
if (expectReports && report == null) {
|
||||||
} catch (JsonException e) {
|
_log.Error($"unable to parse report ({filePath}) as a report or regression");
|
||||||
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;
|
return null;
|
||||||
}
|
}
|
||||||
|
return report;
|
||||||
}
|
}
|
||||||
|
return regressionReport;
|
||||||
}
|
}
|
||||||
|
|
||||||
private RegressionReportOrReport? ParseReportOrRegression(IEnumerable<byte> content, string? filePath, bool expectReports = false) {
|
private IReport? ParseReportOrRegression(IEnumerable<byte> content, string? filePath, bool expectReports = false) {
|
||||||
try {
|
try {
|
||||||
var str = System.Text.Encoding.UTF8.GetString(content.ToArray());
|
var str = System.Text.Encoding.UTF8.GetString(content.ToArray());
|
||||||
return ParseReportOrRegression(str, filePath, expectReports);
|
return ParseReportOrRegression(str, filePath, expectReports);
|
||||||
@ -68,7 +62,4 @@ public class Reports : IReports {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class RegressionReportOrReport {
|
public interface IReport { };
|
||||||
public RegressionReport? RegressionReport { get; set; }
|
|
||||||
public Report? Report { get; set; }
|
|
||||||
}
|
|
||||||
|
@ -56,4 +56,12 @@ class TestCreds : ICreds {
|
|||||||
public Task<T> QueryMicrosoftGraph<T>(HttpMethod method, string resource) {
|
public Task<T> QueryMicrosoftGraph<T>(HttpMethod method, string resource) {
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public GenericResource ParseResourceId(string resourceId) {
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<GenericResource> GetData(GenericResource resource) {
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -300,6 +300,41 @@ namespace Tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Gen<NoReproReport> NoReproReport() {
|
||||||
|
return Arb.Generate<Tuple<string, BlobRef?, string?, Guid, int>>().Select(
|
||||||
|
arg =>
|
||||||
|
new NoReproReport(
|
||||||
|
arg.Item1,
|
||||||
|
arg.Item2,
|
||||||
|
arg.Item3,
|
||||||
|
arg.Item4,
|
||||||
|
arg.Item4,
|
||||||
|
arg.Item5,
|
||||||
|
arg.Item3
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Gen<CrashTestResult> CrashTestResult() {
|
||||||
|
return Arb.Generate<Tuple<Report, NoReproReport>>().Select(
|
||||||
|
arg =>
|
||||||
|
new CrashTestResult(
|
||||||
|
arg.Item1,
|
||||||
|
arg.Item2
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Gen<RegressionReport> RegressionReport() {
|
||||||
|
return Arb.Generate<Tuple<CrashTestResult, CrashTestResult?>>().Select(
|
||||||
|
arg =>
|
||||||
|
new RegressionReport(
|
||||||
|
arg.Item1,
|
||||||
|
arg.Item2
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public static Gen<Container> Container() {
|
public static Gen<Container> Container() {
|
||||||
return Arb.Generate<Tuple<NonNull<string>>>().Select(
|
return Arb.Generate<Tuple<NonNull<string>>>().Select(
|
||||||
arg => new Container(string.Join("", arg.Item1.Get.Where(c => char.IsLetterOrDigit(c) || c == '-'))!)
|
arg => new Container(string.Join("", arg.Item1.Get.Where(c => char.IsLetterOrDigit(c) || c == '-'))!)
|
||||||
@ -801,7 +836,7 @@ namespace Tests {
|
|||||||
|
|
||||||
|
|
||||||
[Property]
|
[Property]
|
||||||
public bool RegressionReportOrReport(RegressionReportOrReport e) {
|
public bool RegressionReport(RegressionReport e) {
|
||||||
return Test(e);
|
return Test(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,7 +99,6 @@ public class TimerReproTests {
|
|||||||
return new Repro(
|
return new Repro(
|
||||||
Guid.NewGuid(),
|
Guid.NewGuid(),
|
||||||
Guid.Empty,
|
Guid.Empty,
|
||||||
Guid.Empty,
|
|
||||||
new ReproConfig(
|
new ReproConfig(
|
||||||
new Container(String.Empty),
|
new Container(String.Empty),
|
||||||
String.Empty,
|
String.Empty,
|
||||||
|
Reference in New Issue
Block a user