mirror of
https://github.com/microsoft/onefuzz.git
synced 2025-06-11 09:41:37 +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:
parent
97910e4793
commit
aad29295e1
@ -327,30 +327,33 @@ public class ReproOperations : StatefulOrm<Repro, VmState, ReproOperations>, IRe
|
||||
|
||||
public async Task<OneFuzzResult<Repro>> Create(ReproConfig config, UserInfo userInfo) {
|
||||
var reportOrRegression = await _context.Reports.GetReportOrRegression(config.Container, config.Path);
|
||||
if (reportOrRegression is 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 {
|
||||
if (reportOrRegression is not Report 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) {
|
||||
|
@ -14,7 +14,7 @@ public interface IRequestHandling {
|
||||
}
|
||||
|
||||
// See: https://www.rfc-editor.org/rfc/rfc7807#section-3
|
||||
public sealed class ProblemDetails {
|
||||
public sealed record ProblemDetails {
|
||||
[JsonConstructor]
|
||||
public ProblemDetails(int status, string title, string detail) {
|
||||
Status = status;
|
||||
@ -45,14 +45,14 @@ public sealed class ProblemDetails {
|
||||
/// change from occurrence to occurrence of the problem, except for purposes
|
||||
/// of localization (e.g., using proactive content negotiation; see
|
||||
/// [RFC7231], Section 3.4).
|
||||
public string Title { get; set; }
|
||||
public string Title { get; }
|
||||
|
||||
/// The HTTP status code ([RFC7231], Section 6) generated by the origin
|
||||
/// 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.
|
||||
public string Detail { get; set; }
|
||||
public string Detail { get; }
|
||||
}
|
||||
|
||||
public class RequestHandling : IRequestHandling {
|
||||
|
@ -36,6 +36,8 @@ public sealed class TestContext : IOnefuzzContext {
|
||||
ConfigOperations = new ConfigOperations(logTracer, this, cache);
|
||||
PoolOperations = new PoolOperations(logTracer, this);
|
||||
ScalesetOperations = new ScalesetOperations(logTracer, this);
|
||||
ReproOperations = new ReproOperations(logTracer, this);
|
||||
Reports = new Reports(logTracer, Containers);
|
||||
UserCredentials = new UserCredentials(logTracer, ConfigOperations);
|
||||
}
|
||||
|
||||
@ -49,6 +51,7 @@ public sealed class TestContext : IOnefuzzContext {
|
||||
Node n => NodeOperations.Insert(n),
|
||||
Pool p => PoolOperations.Insert(p),
|
||||
Job j => JobOperations.Insert(j),
|
||||
Repro r => ReproOperations.Insert(r),
|
||||
NodeTasks nt => NodeTasksOperations.Insert(nt),
|
||||
InstanceConfig ic => ConfigOperations.Insert(ic),
|
||||
_ => 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 IScalesetOperations ScalesetOperations { get; }
|
||||
public IVmssOperations VmssOperations { get; }
|
||||
public IReproOperations ReproOperations { get; }
|
||||
public IReports Reports { get; }
|
||||
public EntityConverter EntityConverter { get; }
|
||||
|
||||
// -- Remainder not implemented --
|
||||
@ -100,9 +105,6 @@ public sealed class TestContext : IOnefuzzContext {
|
||||
|
||||
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();
|
||||
|
||||
|
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);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user