Skip to content
This repository has been archived by the owner on Nov 1, 2023. It is now read-only.

Commit

Permalink
Implement node function
Browse files Browse the repository at this point in the history
  • Loading branch information
Porges authored Jun 22, 2022
1 parent 1b874d6 commit 7544718
Show file tree
Hide file tree
Showing 30 changed files with 774 additions and 110 deletions.
179 changes: 179 additions & 0 deletions src/ApiService/ApiService/Node.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
using System.Threading.Tasks;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
using Microsoft.OneFuzz.Service.OneFuzzLib.Orm;

namespace Microsoft.OneFuzz.Service;

public class NodeFunction {
private readonly ILogTracer _log;
private readonly IEndpointAuthorization _auth;
private readonly IOnefuzzContext _context;

public NodeFunction(ILogTracer log, IEndpointAuthorization auth, IOnefuzzContext context) {
_log = log;
_auth = auth;
_context = context;
}

private static readonly EntityConverter _entityConverter = new();

// [Function("Node")
public Async.Task<HttpResponseData> Run([HttpTrigger("GET", "PATCH", "POST", "DELETE")] HttpRequestData req) {
return _auth.CallIfUser(req, r => r.Method switch {
"GET" => Get(r),
"PATCH" => Patch(r),
"POST" => Post(r),
"DELETE" => Delete(r),
_ => throw new InvalidOperationException("Unsupported HTTP method"),
});
}

private async Async.Task<HttpResponseData> Get(HttpRequestData req) {
var request = await RequestHandling.ParseRequest<NodeSearch>(req);
if (!request.IsOk) {
return await _context.RequestHandling.NotOk(req, request.ErrorV, "pool get");
}

var search = request.OkV;
if (search.MachineId is Guid machineId) {
var node = await _context.NodeOperations.GetByMachineId(machineId);
if (node is null) {
return await _context.RequestHandling.NotOk(
req,
new Error(
Code: ErrorCode.UNABLE_TO_FIND,
Errors: new string[] { "unable to find node " }),
context: machineId.ToString());
}

var (tasks, messages) = await (
_context.NodeTasksOperations.GetByMachineId(machineId).ToListAsync().AsTask(),
_context.NodeMessageOperations.GetMessage(machineId).ToListAsync().AsTask());

var commands = messages.Select(m => m.Message).ToList();
return await RequestHandling.Ok(req, NodeToNodeSearchResult(node with { Tasks = tasks, Messages = commands }));
}

var nodes = await _context.NodeOperations.SearchStates(
states: search.State,
poolName: search.PoolName,
scaleSetId: search.ScalesetId).ToListAsync();

return await RequestHandling.Ok(req, nodes.Select(NodeToNodeSearchResult));
}

private static NodeSearchResult NodeToNodeSearchResult(Node node) {
return new NodeSearchResult(
PoolId: node.PoolId,
PoolName: node.PoolName,
MachineId: node.MachineId,
Version: node.Version,
Heartbeat: node.Heartbeat,
InitializedAt: node.InitializedAt,
State: node.State,
ScalesetId: node.ScalesetId,
ReimageRequested: node.ReimageRequested,
DeleteRequested: node.DeleteRequested,
DebugKeepNode: node.DebugKeepNode);
}

private async Async.Task<HttpResponseData> Patch(HttpRequestData req) {
var request = await RequestHandling.ParseRequest<NodeGet>(req);
if (!request.IsOk) {
return await _context.RequestHandling.NotOk(
req,
request.ErrorV,
"NodeReimage");
}

var authCheck = await _auth.CheckRequireAdmins(req);
if (!authCheck.IsOk) {
return await _context.RequestHandling.NotOk(req, authCheck.ErrorV, "NodeReimage");
}

var patch = request.OkV;
var node = await _context.NodeOperations.GetByMachineId(patch.MachineId);
if (node is null) {
return await _context.RequestHandling.NotOk(
req,
new Error(
Code: ErrorCode.UNABLE_TO_FIND,
Errors: new string[] { "unable to find node " }),
context: patch.MachineId.ToString());
}

await _context.NodeOperations.Stop(node, done: true);
if (node.DebugKeepNode) {
await _context.NodeOperations.Replace(node with { DebugKeepNode = false });
}

return await RequestHandling.Ok(req, true);
}

private async Async.Task<HttpResponseData> Post(HttpRequestData req) {
var request = await RequestHandling.ParseRequest<NodeUpdate>(req);
if (!request.IsOk) {
return await _context.RequestHandling.NotOk(
req,
request.ErrorV,
"NodeUpdate");
}

var authCheck = await _auth.CheckRequireAdmins(req);
if (!authCheck.IsOk) {
return await _context.RequestHandling.NotOk(req, authCheck.ErrorV, "NodeUpdate");
}

var post = request.OkV;
var node = await _context.NodeOperations.GetByMachineId(post.MachineId);
if (node is null) {
return await _context.RequestHandling.NotOk(
req,
new Error(
Code: ErrorCode.UNABLE_TO_FIND,
Errors: new string[] { "unable to find node " }),
context: post.MachineId.ToString());
}

if (post.DebugKeepNode is bool value) {
node = node with { DebugKeepNode = value };
}

await _context.NodeOperations.Replace(node);
return await RequestHandling.Ok(req, true);
}

private async Async.Task<HttpResponseData> Delete(HttpRequestData req) {
var request = await RequestHandling.ParseRequest<NodeGet>(req);
if (!request.IsOk) {
return await _context.RequestHandling.NotOk(
req,
request.ErrorV,
context: "NodeDelete");
}

var authCheck = await _auth.CheckRequireAdmins(req);
if (!authCheck.IsOk) {
return await _context.RequestHandling.NotOk(req, authCheck.ErrorV, "NodeDelete");
}

var delete = request.OkV;
var node = await _context.NodeOperations.GetByMachineId(delete.MachineId);
if (node is null) {
return await _context.RequestHandling.NotOk(
req,
new Error(
Code: ErrorCode.UNABLE_TO_FIND,
new string[] { "unable to find node" }),
context: delete.MachineId.ToString());
}

await _context.NodeOperations.SetHalt(node);
if (node.DebugKeepNode) {
await _context.NodeOperations.Replace(node with { DebugKeepNode = false });
}

return await RequestHandling.Ok(req, true);
}
}
1 change: 0 additions & 1 deletion src/ApiService/ApiService/OneFuzzTypes/Events.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.OneFuzz.Service.OneFuzzLib.Orm;
using PoolName = System.String;
using Region = System.String;

namespace Microsoft.OneFuzz.Service;
Expand Down
20 changes: 6 additions & 14 deletions src/ApiService/ApiService/OneFuzzTypes/Model.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
using Microsoft.OneFuzz.Service.OneFuzzLib.Orm;
using Endpoint = System.String;
using GroupId = System.Guid;
using PoolName = System.String;
using PrincipalId = System.Guid;
using Region = System.String;

Expand Down Expand Up @@ -152,7 +151,6 @@ public record Error(ErrorCode Code, string[]? Errors = null);

public record UserInfo(Guid? ApplicationId, Guid? ObjectId, String? Upn);


public record TaskDetails(
TaskType Type,
int Duration,
Expand Down Expand Up @@ -316,11 +314,11 @@ public record InstanceConfig
NetworkSecurityGroupConfig ProxyNsgConfig,
AzureVmExtensionConfig? Extensions,
string ProxyVmSku,
IDictionary<Endpoint, ApiAccessRule>? ApiAccessRules,
IDictionary<PrincipalId, GroupId[]>? GroupMembership,

IDictionary<string, string>? VmTags,
IDictionary<string, string>? VmssTags
IDictionary<Endpoint, ApiAccessRule>? ApiAccessRules = null,
IDictionary<PrincipalId, GroupId[]>? GroupMembership = null,
IDictionary<string, string>? VmTags = null,
IDictionary<string, string>? VmssTags = null,
bool? RequireAdminPrivileges = null
) : EntityBase() {
public InstanceConfig(string instanceName) : this(
instanceName,
Expand All @@ -330,12 +328,7 @@ public InstanceConfig(string instanceName) : this(
new NetworkConfig(),
new NetworkSecurityGroupConfig(),
null,
"Standard_B2s",
null,
null,
null,
null) { }

"Standard_B2s") { }
public InstanceConfig() : this(String.Empty) { }

public List<Guid>? CheckAdmins(List<Guid>? value) {
Expand All @@ -346,7 +339,6 @@ public InstanceConfig() : this(String.Empty) { }
}
}


//# At the moment, this only checks allowed_aad_tenants, however adding
//# support for 3rd party JWT validation is anticipated in a future release.
public ResultVoid<List<string>> CheckInstanceConfig() {
Expand Down
16 changes: 16 additions & 0 deletions src/ApiService/ApiService/OneFuzzTypes/Requests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,22 @@ public record NodeCommandDelete(
string MessageId
) : BaseRequest;

public record NodeGet(
Guid MachineId
) : BaseRequest;

public record NodeUpdate(
Guid MachineId,
bool? DebugKeepNode
) : BaseRequest;

public record NodeSearch(
Guid? MachineId = null,
List<NodeState>? State = null,
Guid? ScalesetId = null,
PoolName? PoolName = null
) : BaseRequest;

public record NodeStateEnvelope(
NodeEventBase Event,
Guid MachineId
Expand Down
22 changes: 21 additions & 1 deletion src/ApiService/ApiService/OneFuzzTypes/Responses.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@
namespace Microsoft.OneFuzz.Service;

[JsonConverter(typeof(BaseResponseConverter))]
public abstract record BaseResponse();
public abstract record BaseResponse() {
public static implicit operator BaseResponse(bool value)
=> new BoolResult(value);
};

public record CanSchedule(
bool Allowed,
Expand All @@ -15,6 +18,23 @@ public record PendingNodeCommand(
NodeCommandEnvelope? Envelope
) : BaseResponse();

// TODO: not sure how much of this is actually
// needed in the search results, so at the moment
// it is a copy of the whole Node type
public record NodeSearchResult(
PoolName PoolName,
Guid MachineId,
Guid? PoolId,
string Version,
DateTimeOffset? Heartbeat,
DateTimeOffset? InitializedAt,
NodeState State,
Guid? ScalesetId,
bool ReimageRequested,
bool DeleteRequested,
bool DebugKeepNode
) : BaseResponse();

public record BoolResult(
bool Result
) : BaseResponse();
Expand Down
8 changes: 7 additions & 1 deletion src/ApiService/ApiService/OneFuzzTypes/ReturnTypes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ public struct OneFuzzResult<T_Ok> {
public static OneFuzzResult<T_Ok> Error(ErrorCode errorCode, string error) => new(errorCode, new[] { error });

public static OneFuzzResult<T_Ok> Error(Error err) => new(err);

// Allow simple conversion of Errors to Results.
public static implicit operator OneFuzzResult<T_Ok>(Error err) => new(err);
}

public struct OneFuzzResultVoid {
Expand All @@ -69,9 +72,12 @@ public struct OneFuzzResultVoid {

private OneFuzzResultVoid(Error err) => (ErrorV, IsOk) = (err, false);

public static OneFuzzResultVoid Ok() => new();
public static OneFuzzResultVoid Ok => new();
public static OneFuzzResultVoid Error(ErrorCode errorCode, string[] errors) => new(errorCode, errors);
public static OneFuzzResultVoid Error(ErrorCode errorCode, string error) => new(errorCode, new[] { error });
public static OneFuzzResultVoid Error(Error err) => new(err);

// Allow simple conversion of Errors to Results.
public static implicit operator OneFuzzResultVoid(Error err) => new(err);
}
}
Loading

0 comments on commit 7544718

Please sign in to comment.