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

Commit

Permalink
More implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
Porges authored Jul 26, 2022
1 parent 6da3325 commit 647c624
Show file tree
Hide file tree
Showing 7 changed files with 114 additions and 32 deletions.
42 changes: 38 additions & 4 deletions src/ApiService/ApiService/Functions/Scaleset.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,41 @@ private async Task<HttpResponseData> Patch(HttpRequestData req) {
if (!request.IsOk) {
return await _context.RequestHandling.NotOk(req, request.ErrorV, "ScalesetUpdate");
}
throw new NotImplementedException();

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

var scalesetResult = await _context.ScalesetOperations.GetById(request.OkV.ScalesetId);
if (!scalesetResult.IsOk) {
return await _context.RequestHandling.NotOk(req, scalesetResult.ErrorV, "ScalesetUpdate");
}

var scaleset = scalesetResult.OkV;
if (!scaleset.State.CanUpdate()) {
return await _context.RequestHandling.NotOk(
req,
new Error(
Code: ErrorCode.INVALID_REQUEST,
Errors: new[] { $"scaleset must be in one of the following states to update: {string.Join(", ", ScalesetStateHelper.CanUpdateStates)}" }),
"ScalesetUpdate");
}

if (request.OkV.Size is long size) {
var resizedScaleset = await _context.ScalesetOperations.SetSize(scaleset, size);
if (resizedScaleset.IsOk) {
scaleset = resizedScaleset.OkV;
} else {
return await _context.RequestHandling.NotOk(
req,
resizedScaleset.ErrorV,
"ScalesetUpdate");
}
}

scaleset = scaleset with { Auth = null };
return await RequestHandling.Ok(req, ScalesetResponse.ForScaleset(scaleset));
}

private async Task<HttpResponseData> Get(HttpRequestData req) {
Expand All @@ -76,10 +110,10 @@ private async Task<HttpResponseData> Get(HttpRequestData req) {
if (!scalesetResult.IsOk) {
return await _context.RequestHandling.NotOk(req, scalesetResult.ErrorV, "ScalesetSearch");
}

var scaleset = scalesetResult.OkV;

var response = ScalesetSearchResponse.ForScaleset(scaleset);
var response = ScalesetResponse.ForScaleset(scaleset);
response = response with { Nodes = await _context.ScalesetOperations.GetNodes(scaleset) };
if (!search.IncludeAuth) {
response = response with { Auth = null };
Expand All @@ -91,7 +125,7 @@ private async Task<HttpResponseData> Get(HttpRequestData req) {
var states = search.State ?? Enumerable.Empty<ScalesetState>();
var scalesets = await _context.ScalesetOperations.SearchStates(states).ToListAsync();
// don't return auths during list actions, only 'get'
var result = scalesets.Select(ss => ScalesetSearchResponse.ForScaleset(ss with { Auth = null}));
var result = scalesets.Select(ss => ScalesetResponse.ForScaleset(ss with { Auth = null }));
return await RequestHandling.Ok(req, result);
}
}
40 changes: 30 additions & 10 deletions src/ApiService/ApiService/OneFuzzTypes/Enums.cs
Original file line number Diff line number Diff line change
Expand Up @@ -138,29 +138,49 @@ public static class JobStateHelper {


public static class ScalesetStateHelper {
private static readonly IReadOnlySet<ScalesetState> _canUpdate = new HashSet<ScalesetState> { ScalesetState.Init, ScalesetState.Resize };
private static readonly IReadOnlySet<ScalesetState> _needsWork =
new HashSet<ScalesetState>{
private static readonly HashSet<ScalesetState> _canUpdate =
new() {
ScalesetState.Init,
ScalesetState.Resize,
};

private static readonly HashSet<ScalesetState> _needsWork =
new() {
ScalesetState.Init,
ScalesetState.Setup,
ScalesetState.Resize,
ScalesetState.Shutdown,
ScalesetState.Halt
ScalesetState.Halt,
};

private static readonly HashSet<ScalesetState> _available =
new() {
ScalesetState.Resize,
ScalesetState.Running,
};

private static readonly HashSet<ScalesetState> _resizing =
new() {
ScalesetState.Halt,
ScalesetState.Init,
ScalesetState.Setup,
};
private static readonly IReadOnlySet<ScalesetState> _available = new HashSet<ScalesetState> { ScalesetState.Resize, ScalesetState.Running };
private static readonly IReadOnlySet<ScalesetState> _resizing = new HashSet<ScalesetState> { ScalesetState.Halt, ScalesetState.Init, ScalesetState.Setup };

/// set of states that indicate the scaleset can be updated
public static IReadOnlySet<ScalesetState> CanUpdate => _canUpdate;
public static bool CanUpdate(this ScalesetState state) => _canUpdate.Contains(state);
public static IReadOnlySet<ScalesetState> CanUpdateStates => _canUpdate;

/// set of states that indicate work is needed during eventing
public static IReadOnlySet<ScalesetState> NeedsWork => _needsWork;
public static bool NeedsWork(this ScalesetState state) => _needsWork.Contains(state);
public static IReadOnlySet<ScalesetState> NeedsWorkStates => _needsWork;

/// set of states that indicate if it's available for work
public static IReadOnlySet<ScalesetState> Available => _available;
public static bool IsAvailable(this ScalesetState state) => _available.Contains(state);
public static IReadOnlySet<ScalesetState> AvailableStates => _available;

/// set of states that indicate scaleset is resizing
public static IReadOnlySet<ScalesetState> Resizing => _resizing;
public static bool IsResizing(this ScalesetState state) => _resizing.Contains(state);
public static IReadOnlySet<ScalesetState> ResizingStates => _resizing;
}


Expand Down
2 changes: 1 addition & 1 deletion src/ApiService/ApiService/OneFuzzTypes/Model.cs
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,7 @@ public record Scaleset(
Guid? ClientId,
Guid? ClientObjectId,
Dictionary<string, string> Tags
// 'Nodes' removed when porting from Python: only used in search response
// 'Nodes' removed when porting from Python: only used in search response
) : StatefulEntityBase<ScalesetState>(State);

[JsonConverter(typeof(ContainerConverter))]
Expand Down
6 changes: 3 additions & 3 deletions src/ApiService/ApiService/OneFuzzTypes/Responses.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ public static JobResponse ForJob(Job j)
);
}

public record ScalesetSearchResponse(
public record ScalesetResponse(
PoolName PoolName,
Guid ScalesetId,
ScalesetState State,
Expand All @@ -109,8 +109,8 @@ public record ScalesetSearchResponse(
Dictionary<string, string> Tags,
List<ScalesetNodeState>? Nodes
) : BaseResponse() {
public static ScalesetSearchResponse ForScaleset(Scaleset s)
=> new (
public static ScalesetResponse ForScaleset(Scaleset s)
=> new(
PoolName: s.PoolName,
ScalesetId: s.ScalesetId,
State: s.State,
Expand Down
10 changes: 5 additions & 5 deletions src/ApiService/ApiService/onefuzzlib/NodeOperations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -131,25 +131,25 @@ public async Task<bool> CanProcessNewWork(Node node) {

if (node.ScalesetId != null) {
var scalesetResult = await _context.ScalesetOperations.GetById(node.ScalesetId.Value);
if (!scalesetResult.IsOk || scalesetResult.OkV == null) {
if (!scalesetResult.IsOk) {
_logTracer.Info($"can_process_new_work invalid scaleset. scaleset_id:{node.ScalesetId} machine_id:{node.MachineId}");
return false;
}
var scaleset = scalesetResult.OkV!;

if (!ScalesetStateHelper.Available.Contains(scaleset.State)) {
var scaleset = scalesetResult.OkV;
if (!scaleset.State.IsAvailable()) {
_logTracer.Info($"can_process_new_work scaleset not available for work. scaleset_id:{node.ScalesetId} machine_id:{node.MachineId}");
return false;
}
}

var poolResult = await _context.PoolOperations.GetByName(node.PoolName);
if (!poolResult.IsOk || poolResult.OkV == null) {
if (!poolResult.IsOk) {
_logTracer.Info($"can_schedule - invalid pool. pool_name:{node.PoolName} machine_id:{node.MachineId}");
return false;
}

var pool = poolResult.OkV!;
var pool = poolResult.OkV;
if (!PoolStateHelper.Available.Contains(pool.State)) {
_logTracer.Info($"can_schedule - pool is not available for work. pool_name:{node.PoolName} machine_id:{node.MachineId}");
return false;
Expand Down
41 changes: 35 additions & 6 deletions src/ApiService/ApiService/onefuzzlib/ScalesetOperations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public interface IScalesetOperations : IOrm<Scaleset> {
public Async.Task<List<ScalesetNodeState>> GetNodes(Scaleset scaleset);
IAsyncEnumerable<Scaleset> SearchStates(IEnumerable<ScalesetState> states);
Async.Task SetShutdown(Scaleset scaleset, bool now);
Async.Task<OneFuzzResult<Scaleset>> SetSize(Scaleset scaleset, long size);
}

public class ScalesetOperations : StatefulOrm<Scaleset, ScalesetState, ScalesetOperations>, IScalesetOperations {
Expand All @@ -37,17 +38,24 @@ public IAsyncEnumerable<Scaleset> SearchByPool(PoolName poolName) {
}


async Async.Task SetState(Scaleset scaleSet, ScalesetState state) {
if (scaleSet.State == state)
return;
async Async.Task<OneFuzzResult<Scaleset>> SetState(Scaleset scaleSet, ScalesetState state) {
if (scaleSet.State == state) {
return OneFuzzResult.Ok(scaleSet);
}

if (scaleSet.State == ScalesetState.Halt)
return;
{
// terminal state, unable to change
// TODO: should this throw an exception instead?
return OneFuzzResult.Ok(scaleSet);
}

var updatedScaleSet = scaleSet with { State = state };
var r = await this.Replace(updatedScaleSet);
var r = await Update(updatedScaleSet);
if (!r.IsOk) {
_log.Error($"Failed to update scaleset {scaleSet.ScalesetId} when updating state from {scaleSet.State} to {state}");
var msg = "Failed to update scaleset {scaleSet.ScalesetId} when updating state from {scaleSet.State} to {state}";
_log.Error(msg);
return OneFuzzResult<Scaleset>.Error(ErrorCode.UNABLE_TO_UPDATE, msg);
}

if (state == ScalesetState.Resize) {
Expand All @@ -59,6 +67,8 @@ await _context.Events.SendEvent(
new EventScalesetStateUpdated(updatedScaleSet.ScalesetId, updatedScaleSet.PoolName, updatedScaleSet.State)
);
}

return OneFuzzResult.Ok(scaleSet);
}

async Async.Task SetFailed(Scaleset scaleSet, Error error) {
Expand Down Expand Up @@ -365,4 +375,23 @@ public IAsyncEnumerable<Scaleset> SearchStates(IEnumerable<ScalesetState> states

public Async.Task SetShutdown(Scaleset scaleset, bool now)
=> SetState(scaleset, now ? ScalesetState.Halt : ScalesetState.Shutdown);

public Async.Task<OneFuzzResult<Scaleset>> SetSize(Scaleset scaleset, long size) {
var permittedSize = Math.Min(size, MaxSize(scaleset));
if (permittedSize == scaleset.Size) {
return Async.Task.FromResult(OneFuzzResult.Ok(scaleset)); // nothing to do
}

scaleset = scaleset with { Size = permittedSize };
return SetState(scaleset, ScalesetState.Resize);
}

private static long MaxSize(Scaleset scaleset) {
// https://docs.microsoft.com/en-us/azure/virtual-machine-scale-sets/virtual-machine-scale-sets-placement-groups#checklist-for-using-large-scale-sets
if (scaleset.Image.StartsWith("/", StringComparison.Ordinal)) {
return 600;
} else {
return 1000;
}
}
}
5 changes: 2 additions & 3 deletions src/ApiService/Tests/OrmModelsTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ public static Gen<Task> Task() {
public static Gen<Scaleset> Scaleset { get; }
= from arg in Arb.Generate<Tuple<
Tuple<Guid, ScalesetState, Authentication?, string, string, string>,
Tuple<int, bool, bool, bool, Error?, List<ScalesetNodeState>, Guid?>,
Tuple<int, bool, bool, bool, Error?, Guid?>,
Tuple<Guid?, Dictionary<string, string>>>>()
from poolName in PoolNameGen
select new Scaleset(
Expand All @@ -225,8 +225,7 @@ from poolName in PoolNameGen
EphemeralOsDisks: arg.Item2.Item3,
NeedsConfigUpdate: arg.Item2.Item4,
Error: arg.Item2.Item5,
Nodes: arg.Item2.Item6,
ClientId: arg.Item2.Item7,
ClientId: arg.Item2.Item6,

ClientObjectId: arg.Item3.Item1,
Tags: arg.Item3.Item2);
Expand Down

0 comments on commit 647c624

Please sign in to comment.