mirror of
https://github.com/microsoft/onefuzz.git
synced 2025-06-12 18:18:08 +00:00
Repro Create should fail if insert fails, add tests (#2678)
At the moment the result of the insert is ignored.
This commit is contained in:
@ -327,30 +327,33 @@ public class ReproOperations : StatefulOrm<Repro, VmState, ReproOperations>, IRe
|
|||||||
|
|
||||||
public async Task<OneFuzzResult<Repro>> Create(ReproConfig config, UserInfo userInfo) {
|
public async Task<OneFuzzResult<Repro>> Create(ReproConfig config, UserInfo userInfo) {
|
||||||
var reportOrRegression = await _context.Reports.GetReportOrRegression(config.Container, config.Path);
|
var reportOrRegression = await _context.Reports.GetReportOrRegression(config.Container, config.Path);
|
||||||
if (reportOrRegression is Report report) {
|
if (reportOrRegression is not Report report) {
|
||||||
var task = await _context.TaskOperations.GetByTaskId(report.TaskId);
|
|
||||||
if (task == null) {
|
|
||||||
return OneFuzzResult<Repro>.Error(ErrorCode.INVALID_REQUEST, "unable to find task");
|
|
||||||
}
|
|
||||||
|
|
||||||
var vm = new Repro(
|
|
||||||
VmId: Guid.NewGuid(),
|
|
||||||
Config: config,
|
|
||||||
TaskId: task.TaskId,
|
|
||||||
Os: task.Os,
|
|
||||||
Auth: await Auth.BuildAuth(_logTracer),
|
|
||||||
EndTime: DateTimeOffset.UtcNow + TimeSpan.FromHours(config.Duration),
|
|
||||||
UserInfo: userInfo
|
|
||||||
);
|
|
||||||
|
|
||||||
var r = await _context.ReproOperations.Insert(vm);
|
|
||||||
if (!r.IsOk) {
|
|
||||||
_logTracer.WithHttpStatus(r.ErrorV).Error($"failed to insert repro record for {vm.VmId:Tag:VmId}");
|
|
||||||
}
|
|
||||||
return OneFuzzResult<Repro>.Ok(vm);
|
|
||||||
} else {
|
|
||||||
return OneFuzzResult<Repro>.Error(ErrorCode.UNABLE_TO_FIND, "unable to find report");
|
return OneFuzzResult<Repro>.Error(ErrorCode.UNABLE_TO_FIND, "unable to find report");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var task = await _context.TaskOperations.GetByTaskId(report.TaskId);
|
||||||
|
if (task is null) {
|
||||||
|
return OneFuzzResult<Repro>.Error(ErrorCode.INVALID_REQUEST, "unable to find task");
|
||||||
|
}
|
||||||
|
|
||||||
|
var vm = new Repro(
|
||||||
|
VmId: Guid.NewGuid(),
|
||||||
|
Config: config,
|
||||||
|
TaskId: task.TaskId,
|
||||||
|
Os: task.Os,
|
||||||
|
Auth: await Auth.BuildAuth(_logTracer),
|
||||||
|
EndTime: DateTimeOffset.UtcNow + TimeSpan.FromHours(config.Duration),
|
||||||
|
UserInfo: userInfo);
|
||||||
|
|
||||||
|
var r = await _context.ReproOperations.Insert(vm);
|
||||||
|
if (!r.IsOk) {
|
||||||
|
_logTracer.WithHttpStatus(r.ErrorV).Error($"failed to insert repro record for {vm.VmId:Tag:VmId}");
|
||||||
|
return OneFuzzResult<Repro>.Error(
|
||||||
|
ErrorCode.UNABLE_TO_CREATE,
|
||||||
|
new[] { "failed to insert repro record" });
|
||||||
|
}
|
||||||
|
|
||||||
|
return OneFuzzResult.Ok(vm);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<Repro> ExtensionsFailed(Repro repro) {
|
public Task<Repro> ExtensionsFailed(Repro repro) {
|
||||||
|
@ -14,7 +14,7 @@ public interface IRequestHandling {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// See: https://www.rfc-editor.org/rfc/rfc7807#section-3
|
// See: https://www.rfc-editor.org/rfc/rfc7807#section-3
|
||||||
public sealed class ProblemDetails {
|
public sealed record ProblemDetails {
|
||||||
[JsonConstructor]
|
[JsonConstructor]
|
||||||
public ProblemDetails(int status, string title, string detail) {
|
public ProblemDetails(int status, string title, string detail) {
|
||||||
Status = status;
|
Status = status;
|
||||||
@ -45,14 +45,14 @@ public sealed class ProblemDetails {
|
|||||||
/// change from occurrence to occurrence of the problem, except for purposes
|
/// change from occurrence to occurrence of the problem, except for purposes
|
||||||
/// of localization (e.g., using proactive content negotiation; see
|
/// of localization (e.g., using proactive content negotiation; see
|
||||||
/// [RFC7231], Section 3.4).
|
/// [RFC7231], Section 3.4).
|
||||||
public string Title { get; set; }
|
public string Title { get; }
|
||||||
|
|
||||||
/// The HTTP status code ([RFC7231], Section 6) generated by the origin
|
/// The HTTP status code ([RFC7231], Section 6) generated by the origin
|
||||||
/// server for this occurrence of the problem.
|
/// server for this occurrence of the problem.
|
||||||
public int Status { get; set; }
|
public int Status { get; }
|
||||||
|
|
||||||
// A human-readable explanation specific to this occurrence of the problem.
|
// A human-readable explanation specific to this occurrence of the problem.
|
||||||
public string Detail { get; set; }
|
public string Detail { get; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class RequestHandling : IRequestHandling {
|
public class RequestHandling : IRequestHandling {
|
||||||
|
@ -36,6 +36,8 @@ public sealed class TestContext : IOnefuzzContext {
|
|||||||
ConfigOperations = new ConfigOperations(logTracer, this, cache);
|
ConfigOperations = new ConfigOperations(logTracer, this, cache);
|
||||||
PoolOperations = new PoolOperations(logTracer, this);
|
PoolOperations = new PoolOperations(logTracer, this);
|
||||||
ScalesetOperations = new ScalesetOperations(logTracer, this);
|
ScalesetOperations = new ScalesetOperations(logTracer, this);
|
||||||
|
ReproOperations = new ReproOperations(logTracer, this);
|
||||||
|
Reports = new Reports(logTracer, Containers);
|
||||||
UserCredentials = new UserCredentials(logTracer, ConfigOperations);
|
UserCredentials = new UserCredentials(logTracer, ConfigOperations);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,6 +51,7 @@ public sealed class TestContext : IOnefuzzContext {
|
|||||||
Node n => NodeOperations.Insert(n),
|
Node n => NodeOperations.Insert(n),
|
||||||
Pool p => PoolOperations.Insert(p),
|
Pool p => PoolOperations.Insert(p),
|
||||||
Job j => JobOperations.Insert(j),
|
Job j => JobOperations.Insert(j),
|
||||||
|
Repro r => ReproOperations.Insert(r),
|
||||||
NodeTasks nt => NodeTasksOperations.Insert(nt),
|
NodeTasks nt => NodeTasksOperations.Insert(nt),
|
||||||
InstanceConfig ic => ConfigOperations.Insert(ic),
|
InstanceConfig ic => ConfigOperations.Insert(ic),
|
||||||
_ => throw new NotSupportedException($"You will need to add an TestContext.InsertAll case for {x.GetType()} entities"),
|
_ => throw new NotSupportedException($"You will need to add an TestContext.InsertAll case for {x.GetType()} entities"),
|
||||||
@ -78,6 +81,8 @@ public sealed class TestContext : IOnefuzzContext {
|
|||||||
public IPoolOperations PoolOperations { get; }
|
public IPoolOperations PoolOperations { get; }
|
||||||
public IScalesetOperations ScalesetOperations { get; }
|
public IScalesetOperations ScalesetOperations { get; }
|
||||||
public IVmssOperations VmssOperations { get; }
|
public IVmssOperations VmssOperations { get; }
|
||||||
|
public IReproOperations ReproOperations { get; }
|
||||||
|
public IReports Reports { get; }
|
||||||
public EntityConverter EntityConverter { get; }
|
public EntityConverter EntityConverter { get; }
|
||||||
|
|
||||||
// -- Remainder not implemented --
|
// -- Remainder not implemented --
|
||||||
@ -100,9 +105,6 @@ public sealed class TestContext : IOnefuzzContext {
|
|||||||
|
|
||||||
public IProxyOperations ProxyOperations => throw new System.NotImplementedException();
|
public IProxyOperations ProxyOperations => throw new System.NotImplementedException();
|
||||||
|
|
||||||
public IReports Reports => throw new System.NotImplementedException();
|
|
||||||
|
|
||||||
public IReproOperations ReproOperations => throw new System.NotImplementedException();
|
|
||||||
|
|
||||||
public IScheduler Scheduler => throw new System.NotImplementedException();
|
public IScheduler Scheduler => throw new System.NotImplementedException();
|
||||||
|
|
||||||
|
249
src/ApiService/IntegrationTests/ReproVmssTests.cs
Normal file
249
src/ApiService/IntegrationTests/ReproVmssTests.cs
Normal file
@ -0,0 +1,249 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Net;
|
||||||
|
using System.Text.Json;
|
||||||
|
using IntegrationTests.Fakes;
|
||||||
|
using Microsoft.OneFuzz.Service;
|
||||||
|
using Microsoft.OneFuzz.Service.Functions;
|
||||||
|
using Microsoft.OneFuzz.Service.OneFuzzLib.Orm;
|
||||||
|
using Xunit;
|
||||||
|
using Xunit.Abstractions;
|
||||||
|
using Async = System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace IntegrationTests.Functions;
|
||||||
|
|
||||||
|
[Trait("Category", "Live")]
|
||||||
|
public class AzureStorageReproVmssTest : ReproVmssTestBase {
|
||||||
|
public AzureStorageReproVmssTest(ITestOutputHelper output)
|
||||||
|
: base(output, Integration.AzureStorage.FromEnvironment()) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AzuriteReproVmssTest : ReproVmssTestBase {
|
||||||
|
public AzuriteReproVmssTest(ITestOutputHelper output)
|
||||||
|
: base(output, new Integration.AzuriteStorage()) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class ReproVmssTestBase : FunctionTestBase {
|
||||||
|
public ReproVmssTestBase(ITestOutputHelper output, IStorage storage)
|
||||||
|
: base(output, storage) { }
|
||||||
|
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("POST", RequestType.Agent)]
|
||||||
|
[InlineData("POST", RequestType.NoAuthorization)]
|
||||||
|
[InlineData("GET", RequestType.Agent)]
|
||||||
|
[InlineData("GET", RequestType.NoAuthorization)]
|
||||||
|
[InlineData("DELETE", RequestType.Agent)]
|
||||||
|
[InlineData("DELETE", RequestType.NoAuthorization)]
|
||||||
|
public async Async.Task UserAuthorization_IsRequired(string method, RequestType authType) {
|
||||||
|
var auth = new TestEndpointAuthorization(authType, Logger, Context);
|
||||||
|
var func = new ReproVmss(Logger, auth, Context);
|
||||||
|
var result = await func.Run(TestHttpRequestData.Empty(method));
|
||||||
|
Assert.Equal(HttpStatusCode.Unauthorized, result.StatusCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Async.Task GetMissingVmFails() {
|
||||||
|
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
||||||
|
var func = new ReproVmss(Logger, auth, Context);
|
||||||
|
var req = new ReproGet(VmId: Guid.NewGuid());
|
||||||
|
var result = await func.Run(TestHttpRequestData.FromJson("GET", req));
|
||||||
|
// TODO: should this be 404?
|
||||||
|
Assert.Equal(HttpStatusCode.BadRequest, result.StatusCode);
|
||||||
|
var err = BodyAs<ProblemDetails>(result);
|
||||||
|
Assert.Equal("no such VM", err.Detail);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Async.Task GetAvailableVMsCanReturnEmpty() {
|
||||||
|
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
||||||
|
var func = new ReproVmss(Logger, auth, Context);
|
||||||
|
var req = new ReproGet(VmId: null); // this means "all available"
|
||||||
|
var result = await func.Run(TestHttpRequestData.FromJson("GET", req));
|
||||||
|
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
||||||
|
Assert.Empty(BodyAs<Repro[]>(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Async.Task GetAvailableVMsCanReturnVM() {
|
||||||
|
var vmId = Guid.NewGuid();
|
||||||
|
|
||||||
|
await Context.InsertAll(
|
||||||
|
new Repro(
|
||||||
|
VmId: vmId,
|
||||||
|
TaskId: Guid.NewGuid(),
|
||||||
|
new ReproConfig(Container.Parse("abcd"), "", 12345),
|
||||||
|
Auth: null,
|
||||||
|
Os: Os.Linux));
|
||||||
|
|
||||||
|
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
||||||
|
var func = new ReproVmss(Logger, auth, Context);
|
||||||
|
var req = new ReproGet(VmId: null); // this means "all available"
|
||||||
|
var result = await func.Run(TestHttpRequestData.FromJson("GET", req));
|
||||||
|
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
||||||
|
var repro = Assert.Single(BodyAs<Repro[]>(result));
|
||||||
|
Assert.Equal(vmId, repro.VmId);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Async.Task GetAvailableVMsCanReturnSpecificVM() {
|
||||||
|
var vmId = Guid.NewGuid();
|
||||||
|
|
||||||
|
await Context.InsertAll(
|
||||||
|
new Repro(
|
||||||
|
VmId: vmId,
|
||||||
|
TaskId: Guid.NewGuid(),
|
||||||
|
new ReproConfig(Container.Parse("abcd"), "", 12345),
|
||||||
|
Auth: null,
|
||||||
|
Os: Os.Linux));
|
||||||
|
|
||||||
|
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
||||||
|
var func = new ReproVmss(Logger, auth, Context);
|
||||||
|
var req = new ReproGet(VmId: vmId);
|
||||||
|
var result = await func.Run(TestHttpRequestData.FromJson("GET", req));
|
||||||
|
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
||||||
|
Assert.Equal(vmId, BodyAs<Repro>(result).VmId);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Async.Task GetAvailableVMsDoesNotReturnUnavailableVMs() {
|
||||||
|
await Context.InsertAll(
|
||||||
|
new Repro(
|
||||||
|
VmId: Guid.NewGuid(),
|
||||||
|
TaskId: Guid.NewGuid(),
|
||||||
|
new ReproConfig(Container.Parse("abcd"), "", 12345),
|
||||||
|
Auth: null,
|
||||||
|
Os: Os.Linux,
|
||||||
|
State: VmState.Stopping),
|
||||||
|
new Repro(
|
||||||
|
VmId: Guid.NewGuid(),
|
||||||
|
TaskId: Guid.NewGuid(),
|
||||||
|
new ReproConfig(Container.Parse("abcd"), "", 12345),
|
||||||
|
Auth: null,
|
||||||
|
Os: Os.Linux,
|
||||||
|
State: VmState.Stopped));
|
||||||
|
|
||||||
|
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
||||||
|
var func = new ReproVmss(Logger, auth, Context);
|
||||||
|
var req = new ReproGet(VmId: null); // this means "all available"
|
||||||
|
var result = await func.Run(TestHttpRequestData.FromJson("GET", req));
|
||||||
|
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
||||||
|
Assert.Empty(BodyAs<Repro[]>(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Async.Task CannotCreateVMWithoutCredentials() {
|
||||||
|
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
||||||
|
|
||||||
|
var func = new ReproVmss(Logger, auth, Context);
|
||||||
|
var req = new ReproCreate(Container.Parse("abcd"), "/", 12345);
|
||||||
|
var result = await func.Run(TestHttpRequestData.FromJson("POST", req));
|
||||||
|
Assert.Equal(HttpStatusCode.BadRequest, result.StatusCode);
|
||||||
|
var err = BodyAs<ProblemDetails>(result);
|
||||||
|
Assert.Equal(new ProblemDetails(400, "INVALID_REQUEST", "unable to find authorization token"), err);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Async.Task CannotCreateVMForMissingReport() {
|
||||||
|
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
||||||
|
|
||||||
|
// setup fake user
|
||||||
|
var userInfo = new UserInfo(Guid.NewGuid(), Guid.NewGuid(), "upn");
|
||||||
|
Context.UserCredentials = new TestUserCredentials(Logger, Context.ConfigOperations, OneFuzzResult.Ok(userInfo));
|
||||||
|
|
||||||
|
var func = new ReproVmss(Logger, auth, Context);
|
||||||
|
var req = new ReproCreate(Container.Parse("abcd"), "/", 12345);
|
||||||
|
var result = await func.Run(TestHttpRequestData.FromJson("POST", req));
|
||||||
|
Assert.Equal(HttpStatusCode.BadRequest, result.StatusCode);
|
||||||
|
var err = BodyAs<ProblemDetails>(result);
|
||||||
|
Assert.Equal(new ProblemDetails(400, "UNABLE_TO_FIND", "unable to find report"), err);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Async.Task<(Container, string)> CreateContainerWithReport(Guid jobId, Guid taskId) {
|
||||||
|
var container = Container.Parse(Guid.NewGuid().ToString("N"));
|
||||||
|
var filename = "report.json";
|
||||||
|
// Setup container with Report
|
||||||
|
var cc = GetContainerClient(container);
|
||||||
|
_ = await cc.CreateIfNotExistsAsync();
|
||||||
|
using (var ms = new MemoryStream()) {
|
||||||
|
var emptyReport = new Report(
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
new List<string>(),
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
null,
|
||||||
|
TaskId: taskId,
|
||||||
|
JobId: jobId,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null);
|
||||||
|
|
||||||
|
JsonSerializer.Serialize(ms, emptyReport, EntityConverter.GetJsonSerializerOptions());
|
||||||
|
_ = ms.Seek(0, SeekOrigin.Begin);
|
||||||
|
_ = await cc.UploadBlobAsync(filename, ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (container, filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Async.Task CannotCreateVMForMissingTask() {
|
||||||
|
var (container, filename) = await CreateContainerWithReport(Guid.NewGuid(), Guid.NewGuid());
|
||||||
|
|
||||||
|
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
||||||
|
|
||||||
|
// setup fake user
|
||||||
|
var userInfo = new UserInfo(Guid.NewGuid(), Guid.NewGuid(), "upn");
|
||||||
|
Context.UserCredentials = new TestUserCredentials(Logger, Context.ConfigOperations, OneFuzzResult.Ok(userInfo));
|
||||||
|
|
||||||
|
var func = new ReproVmss(Logger, auth, Context);
|
||||||
|
var req = new ReproCreate(container, filename, 12345);
|
||||||
|
var result = await func.Run(TestHttpRequestData.FromJson("POST", req));
|
||||||
|
Assert.Equal(HttpStatusCode.BadRequest, result.StatusCode);
|
||||||
|
var err = BodyAs<ProblemDetails>(result);
|
||||||
|
Assert.Equal(new ProblemDetails(400, "INVALID_REQUEST", "unable to find task"), err);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Async.Task CanCreateVMSuccessfully() {
|
||||||
|
// report must have TaskID pointing to a valid Task
|
||||||
|
|
||||||
|
var jobId = Guid.NewGuid();
|
||||||
|
var taskId = Guid.NewGuid();
|
||||||
|
var (container, filename) = await CreateContainerWithReport(jobId: jobId, taskId: taskId);
|
||||||
|
await Context.InsertAll(
|
||||||
|
new Task(
|
||||||
|
JobId: jobId,
|
||||||
|
TaskId: taskId,
|
||||||
|
TaskState.Running,
|
||||||
|
Os.Linux,
|
||||||
|
new TaskConfig(
|
||||||
|
JobId: jobId,
|
||||||
|
null,
|
||||||
|
new TaskDetails(TaskType.LibfuzzerFuzz, 12345))));
|
||||||
|
|
||||||
|
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
||||||
|
|
||||||
|
// setup fake user
|
||||||
|
var userInfo = new UserInfo(Guid.NewGuid(), Guid.NewGuid(), "upn");
|
||||||
|
Context.UserCredentials = new TestUserCredentials(Logger, Context.ConfigOperations, OneFuzzResult.Ok(userInfo));
|
||||||
|
|
||||||
|
var func = new ReproVmss(Logger, auth, Context);
|
||||||
|
var req = new ReproCreate(container, filename, 12345);
|
||||||
|
var result = await func.Run(TestHttpRequestData.FromJson("POST", req));
|
||||||
|
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
||||||
|
var repro = BodyAs<Repro>(result);
|
||||||
|
Assert.Equal(taskId, repro.TaskId);
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user