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:
George Pollard 2022-12-09 10:31:40 +13:00 committed by GitHub
parent 97910e4793
commit aad29295e1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 283 additions and 29 deletions

View File

@ -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) {

View File

@ -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 {

View File

@ -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();

View 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);
}
}