mirror of
https://github.com/microsoft/onefuzz.git
synced 2025-06-17 12:28:07 +00:00
Expand valid scaleset names (#3045)
Scaleset names are now permitted to be any (valid) strings, instead of only GUIDs. When we generate a scaleset name it is now based upon the pool name; for example the pool `pool` might get a scaleset named `pool-3b24ba211cad4b078655914754485838`.
This should be backwards-compatible since GUIDs are [already serialized to table storage as strings](dddcfa4949/src/ApiService/ApiService/onefuzzlib/orm/EntityConverter.cs (L190-L191)
), so this simply loosens the restrictions placed upon them.
Scaleset IDs now have a strong type in the same way as other IDs; this helps to avoid mixing them up with other strings. Because of this I found one bug in the scaleset search query logic due to Pool ID/VMSS ID confusion. As part of fixing this I've changed the scaleset search query to only return nodes from the table rather than querying Azure to find a list; this seems to be sufficient for the CLI.
This commit is contained in:
@ -1329,7 +1329,6 @@ If webhook is set to have Event Grid message format then the payload will look a
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"scaleset_id": {
|
"scaleset_id": {
|
||||||
"format": "uuid",
|
|
||||||
"title": "Scaleset Id",
|
"title": "Scaleset Id",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
@ -1369,7 +1368,6 @@ If webhook is set to have Event Grid message format then the payload will look a
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"scaleset_id": {
|
"scaleset_id": {
|
||||||
"format": "uuid",
|
|
||||||
"title": "Scaleset Id",
|
"title": "Scaleset Id",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
@ -1429,7 +1427,6 @@ If webhook is set to have Event Grid message format then the payload will look a
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"scaleset_id": {
|
"scaleset_id": {
|
||||||
"format": "uuid",
|
|
||||||
"title": "Scaleset Id",
|
"title": "Scaleset Id",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
@ -1487,7 +1484,6 @@ If webhook is set to have Event Grid message format then the payload will look a
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"scaleset_id": {
|
"scaleset_id": {
|
||||||
"format": "uuid",
|
|
||||||
"title": "Scaleset Id",
|
"title": "Scaleset Id",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
@ -2597,7 +2593,7 @@ If webhook is set to have Event Grid message format then the payload will look a
|
|||||||
"image": "Canonical:0001-com-ubuntu-server-focal:20_04-lts:latest",
|
"image": "Canonical:0001-com-ubuntu-server-focal:20_04-lts:latest",
|
||||||
"pool_name": "example",
|
"pool_name": "example",
|
||||||
"region": "eastus",
|
"region": "eastus",
|
||||||
"scaleset_id": "00000000-0000-0000-0000-000000000000",
|
"scaleset_id": "example-000",
|
||||||
"size": 10,
|
"size": 10,
|
||||||
"vm_sku": "Standard_D2s_v3"
|
"vm_sku": "Standard_D2s_v3"
|
||||||
}
|
}
|
||||||
@ -2621,7 +2617,6 @@ If webhook is set to have Event Grid message format then the payload will look a
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"scaleset_id": {
|
"scaleset_id": {
|
||||||
"format": "uuid",
|
|
||||||
"title": "Scaleset Id",
|
"title": "Scaleset Id",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
@ -2654,7 +2649,7 @@ If webhook is set to have Event Grid message format then the payload will look a
|
|||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"pool_name": "example",
|
"pool_name": "example",
|
||||||
"scaleset_id": "00000000-0000-0000-0000-000000000000"
|
"scaleset_id": "example-000"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -2668,7 +2663,6 @@ If webhook is set to have Event Grid message format then the payload will look a
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"scaleset_id": {
|
"scaleset_id": {
|
||||||
"format": "uuid",
|
|
||||||
"title": "Scaleset Id",
|
"title": "Scaleset Id",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
@ -2695,7 +2689,7 @@ If webhook is set to have Event Grid message format then the payload will look a
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"pool_name": "example",
|
"pool_name": "example",
|
||||||
"scaleset_id": "00000000-0000-0000-0000-000000000000"
|
"scaleset_id": "example-000"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -2764,7 +2758,6 @@ If webhook is set to have Event Grid message format then the payload will look a
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"scaleset_id": {
|
"scaleset_id": {
|
||||||
"format": "uuid",
|
|
||||||
"title": "Scaleset Id",
|
"title": "Scaleset Id",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
@ -2786,7 +2779,7 @@ If webhook is set to have Event Grid message format then the payload will look a
|
|||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"pool_name": "example",
|
"pool_name": "example",
|
||||||
"scaleset_id": "00000000-0000-0000-0000-000000000000",
|
"scaleset_id": "example-000",
|
||||||
"size": 0
|
"size": 0
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@ -2801,7 +2794,6 @@ If webhook is set to have Event Grid message format then the payload will look a
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"scaleset_id": {
|
"scaleset_id": {
|
||||||
"format": "uuid",
|
|
||||||
"title": "Scaleset Id",
|
"title": "Scaleset Id",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
@ -2827,7 +2819,7 @@ If webhook is set to have Event Grid message format then the payload will look a
|
|||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"pool_name": "example",
|
"pool_name": "example",
|
||||||
"scaleset_id": "00000000-0000-0000-0000-000000000000",
|
"scaleset_id": "example-000",
|
||||||
"state": "init"
|
"state": "init"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@ -2857,7 +2849,6 @@ If webhook is set to have Event Grid message format then the payload will look a
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"scaleset_id": {
|
"scaleset_id": {
|
||||||
"format": "uuid",
|
|
||||||
"title": "Scaleset Id",
|
"title": "Scaleset Id",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
@ -5658,7 +5649,6 @@ If webhook is set to have Event Grid message format then the payload will look a
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"scaleset_id": {
|
"scaleset_id": {
|
||||||
"format": "uuid",
|
|
||||||
"title": "Scaleset Id",
|
"title": "Scaleset Id",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
@ -5682,7 +5672,6 @@ If webhook is set to have Event Grid message format then the payload will look a
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"scaleset_id": {
|
"scaleset_id": {
|
||||||
"format": "uuid",
|
|
||||||
"title": "Scaleset Id",
|
"title": "Scaleset Id",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
@ -5709,7 +5698,6 @@ If webhook is set to have Event Grid message format then the payload will look a
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"scaleset_id": {
|
"scaleset_id": {
|
||||||
"format": "uuid",
|
|
||||||
"title": "Scaleset Id",
|
"title": "Scaleset Id",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
@ -5733,7 +5721,6 @@ If webhook is set to have Event Grid message format then the payload will look a
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"scaleset_id": {
|
"scaleset_id": {
|
||||||
"format": "uuid",
|
|
||||||
"title": "Scaleset Id",
|
"title": "Scaleset Id",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
@ -5926,7 +5913,6 @@ If webhook is set to have Event Grid message format then the payload will look a
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"scaleset_id": {
|
"scaleset_id": {
|
||||||
"format": "uuid",
|
|
||||||
"title": "Scaleset Id",
|
"title": "Scaleset Id",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
@ -5957,7 +5943,6 @@ If webhook is set to have Event Grid message format then the payload will look a
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"scaleset_id": {
|
"scaleset_id": {
|
||||||
"format": "uuid",
|
|
||||||
"title": "Scaleset Id",
|
"title": "Scaleset Id",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
@ -5979,7 +5964,6 @@ If webhook is set to have Event Grid message format then the payload will look a
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"scaleset_id": {
|
"scaleset_id": {
|
||||||
"format": "uuid",
|
|
||||||
"title": "Scaleset Id",
|
"title": "Scaleset Id",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
@ -5999,7 +5983,6 @@ If webhook is set to have Event Grid message format then the payload will look a
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"scaleset_id": {
|
"scaleset_id": {
|
||||||
"format": "uuid",
|
|
||||||
"title": "Scaleset Id",
|
"title": "Scaleset Id",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
@ -6023,7 +6006,6 @@ If webhook is set to have Event Grid message format then the payload will look a
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"scaleset_id": {
|
"scaleset_id": {
|
||||||
"format": "uuid",
|
|
||||||
"title": "Scaleset Id",
|
"title": "Scaleset Id",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
@ -52,7 +52,7 @@ public class Proxy {
|
|||||||
|
|
||||||
var proxyGet = request.OkV;
|
var proxyGet = request.OkV;
|
||||||
switch ((proxyGet.ScalesetId, proxyGet.MachineId, proxyGet.DstPort)) {
|
switch ((proxyGet.ScalesetId, proxyGet.MachineId, proxyGet.DstPort)) {
|
||||||
case (Guid scalesetId, Guid machineId, int dstPort):
|
case (ScalesetId scalesetId, Guid machineId, int dstPort):
|
||||||
var scaleset = await _context.ScalesetOperations.GetById(scalesetId);
|
var scaleset = await _context.ScalesetOperations.GetById(scalesetId);
|
||||||
if (!scaleset.IsOk) {
|
if (!scaleset.IsOk) {
|
||||||
return await _context.RequestHandling.NotOk(req, scaleset.ErrorV, "ProxyGet");
|
return await _context.RequestHandling.NotOk(req, scaleset.ErrorV, "ProxyGet");
|
||||||
|
@ -118,7 +118,7 @@ public class Scaleset {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var scaleset = new Service.Scaleset(
|
var scaleset = new Service.Scaleset(
|
||||||
ScalesetId: Guid.NewGuid(),
|
ScalesetId: Service.Scaleset.GenerateNewScalesetId(create.PoolName),
|
||||||
State: ScalesetState.Init,
|
State: ScalesetState.Init,
|
||||||
NeedsConfigUpdate: false,
|
NeedsConfigUpdate: false,
|
||||||
Auth: await Auth.BuildAuth(_log),
|
Auth: await Auth.BuildAuth(_log),
|
||||||
@ -206,7 +206,7 @@ public class Scaleset {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var search = request.OkV;
|
var search = request.OkV;
|
||||||
if (search.ScalesetId is Guid id) {
|
if (search.ScalesetId is ScalesetId id) {
|
||||||
var scalesetResult = await _context.ScalesetOperations.GetById(id);
|
var scalesetResult = await _context.ScalesetOperations.GetById(id);
|
||||||
if (!scalesetResult.IsOk) {
|
if (!scalesetResult.IsOk) {
|
||||||
return await _context.RequestHandling.NotOk(req, scalesetResult.ErrorV, "ScalesetSearch");
|
return await _context.RequestHandling.NotOk(req, scalesetResult.ErrorV, "ScalesetSearch");
|
||||||
|
@ -185,7 +185,7 @@ public record EventPing(
|
|||||||
|
|
||||||
[EventType(EventType.ScalesetCreated)]
|
[EventType(EventType.ScalesetCreated)]
|
||||||
public record EventScalesetCreated(
|
public record EventScalesetCreated(
|
||||||
Guid ScalesetId,
|
ScalesetId ScalesetId,
|
||||||
PoolName PoolName,
|
PoolName PoolName,
|
||||||
string VmSku,
|
string VmSku,
|
||||||
string Image,
|
string Image,
|
||||||
@ -195,7 +195,7 @@ public record EventScalesetCreated(
|
|||||||
|
|
||||||
[EventType(EventType.ScalesetFailed)]
|
[EventType(EventType.ScalesetFailed)]
|
||||||
public sealed record EventScalesetFailed(
|
public sealed record EventScalesetFailed(
|
||||||
Guid ScalesetId,
|
ScalesetId ScalesetId,
|
||||||
PoolName PoolName,
|
PoolName PoolName,
|
||||||
Error Error
|
Error Error
|
||||||
) : BaseEvent();
|
) : BaseEvent();
|
||||||
@ -203,7 +203,7 @@ public sealed record EventScalesetFailed(
|
|||||||
|
|
||||||
[EventType(EventType.ScalesetDeleted)]
|
[EventType(EventType.ScalesetDeleted)]
|
||||||
public record EventScalesetDeleted(
|
public record EventScalesetDeleted(
|
||||||
Guid ScalesetId,
|
ScalesetId ScalesetId,
|
||||||
PoolName PoolName
|
PoolName PoolName
|
||||||
|
|
||||||
) : BaseEvent();
|
) : BaseEvent();
|
||||||
@ -211,7 +211,7 @@ public record EventScalesetDeleted(
|
|||||||
|
|
||||||
[EventType(EventType.ScalesetResizeScheduled)]
|
[EventType(EventType.ScalesetResizeScheduled)]
|
||||||
public record EventScalesetResizeScheduled(
|
public record EventScalesetResizeScheduled(
|
||||||
Guid ScalesetId,
|
ScalesetId ScalesetId,
|
||||||
PoolName PoolName,
|
PoolName PoolName,
|
||||||
long size
|
long size
|
||||||
) : BaseEvent();
|
) : BaseEvent();
|
||||||
@ -267,14 +267,14 @@ public record EventProxyStateUpdated(
|
|||||||
[EventType(EventType.NodeCreated)]
|
[EventType(EventType.NodeCreated)]
|
||||||
public record EventNodeCreated(
|
public record EventNodeCreated(
|
||||||
Guid MachineId,
|
Guid MachineId,
|
||||||
Guid? ScalesetId,
|
ScalesetId? ScalesetId,
|
||||||
PoolName PoolName
|
PoolName PoolName
|
||||||
) : BaseEvent();
|
) : BaseEvent();
|
||||||
|
|
||||||
[EventType(EventType.NodeHeartbeat)]
|
[EventType(EventType.NodeHeartbeat)]
|
||||||
public record EventNodeHeartbeat(
|
public record EventNodeHeartbeat(
|
||||||
Guid MachineId,
|
Guid MachineId,
|
||||||
Guid? ScalesetId,
|
ScalesetId? ScalesetId,
|
||||||
PoolName PoolName,
|
PoolName PoolName,
|
||||||
NodeState state
|
NodeState state
|
||||||
) : BaseEvent();
|
) : BaseEvent();
|
||||||
@ -283,7 +283,7 @@ public record EventNodeHeartbeat(
|
|||||||
[EventType(EventType.NodeDeleted)]
|
[EventType(EventType.NodeDeleted)]
|
||||||
public record EventNodeDeleted(
|
public record EventNodeDeleted(
|
||||||
Guid MachineId,
|
Guid MachineId,
|
||||||
Guid? ScalesetId,
|
ScalesetId? ScalesetId,
|
||||||
PoolName PoolName,
|
PoolName PoolName,
|
||||||
NodeState? MachineState
|
NodeState? MachineState
|
||||||
) : BaseEvent();
|
) : BaseEvent();
|
||||||
@ -291,7 +291,7 @@ public record EventNodeDeleted(
|
|||||||
|
|
||||||
[EventType(EventType.ScalesetStateUpdated)]
|
[EventType(EventType.ScalesetStateUpdated)]
|
||||||
public record EventScalesetStateUpdated(
|
public record EventScalesetStateUpdated(
|
||||||
Guid ScalesetId,
|
ScalesetId ScalesetId,
|
||||||
PoolName PoolName,
|
PoolName PoolName,
|
||||||
ScalesetState State
|
ScalesetState State
|
||||||
) : BaseEvent();
|
) : BaseEvent();
|
||||||
@ -299,7 +299,7 @@ public record EventScalesetStateUpdated(
|
|||||||
[EventType(EventType.NodeStateUpdated)]
|
[EventType(EventType.NodeStateUpdated)]
|
||||||
public record EventNodeStateUpdated(
|
public record EventNodeStateUpdated(
|
||||||
Guid MachineId,
|
Guid MachineId,
|
||||||
Guid? ScalesetId,
|
ScalesetId? ScalesetId,
|
||||||
PoolName PoolName,
|
PoolName PoolName,
|
||||||
NodeState state
|
NodeState state
|
||||||
) : BaseEvent();
|
) : BaseEvent();
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.OneFuzz.Service.OneFuzzLib.Orm;
|
using Microsoft.OneFuzz.Service.OneFuzzLib.Orm;
|
||||||
using Endpoint = System.String;
|
using Endpoint = System.String;
|
||||||
@ -107,7 +108,7 @@ public record Node
|
|||||||
// a string internally.
|
// a string internally.
|
||||||
string? InstanceId = null,
|
string? InstanceId = null,
|
||||||
|
|
||||||
Guid? ScalesetId = null,
|
ScalesetId? ScalesetId = null,
|
||||||
|
|
||||||
bool ReimageRequested = false,
|
bool ReimageRequested = false,
|
||||||
bool DeleteRequested = false,
|
bool DeleteRequested = false,
|
||||||
@ -132,7 +133,7 @@ public record ProxyForward
|
|||||||
(
|
(
|
||||||
[PartitionKey] Region Region,
|
[PartitionKey] Region Region,
|
||||||
[RowKey] long Port,
|
[RowKey] long Port,
|
||||||
Guid ScalesetId,
|
ScalesetId ScalesetId,
|
||||||
Guid MachineId,
|
Guid MachineId,
|
||||||
Guid? ProxyId,
|
Guid? ProxyId,
|
||||||
long DstPort,
|
long DstPort,
|
||||||
@ -263,7 +264,7 @@ public record TaskEventSummary(
|
|||||||
|
|
||||||
public record NodeAssignment(
|
public record NodeAssignment(
|
||||||
Guid NodeId,
|
Guid NodeId,
|
||||||
Guid? ScalesetId,
|
ScalesetId? ScalesetId,
|
||||||
NodeTaskState State
|
NodeTaskState State
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -392,7 +393,7 @@ public record InstanceConfig
|
|||||||
}
|
}
|
||||||
|
|
||||||
public record AutoScale(
|
public record AutoScale(
|
||||||
[PartitionKey, RowKey] Guid ScalesetId,
|
[PartitionKey, RowKey] ScalesetId ScalesetId,
|
||||||
long Min,
|
long Min,
|
||||||
long Max,
|
long Max,
|
||||||
long Default,
|
long Default,
|
||||||
@ -402,15 +403,10 @@ public record AutoScale(
|
|||||||
long ScaleInCooldown
|
long ScaleInCooldown
|
||||||
) : EntityBase;
|
) : EntityBase;
|
||||||
|
|
||||||
public record ScalesetNodeState(
|
|
||||||
Guid MachineId,
|
|
||||||
string InstanceId,
|
|
||||||
NodeState? State
|
|
||||||
);
|
|
||||||
|
|
||||||
public record Scaleset(
|
public partial record Scaleset(
|
||||||
[PartitionKey] PoolName PoolName,
|
[PartitionKey] PoolName PoolName,
|
||||||
[RowKey] Guid ScalesetId,
|
[RowKey] ScalesetId ScalesetId,
|
||||||
ScalesetState State,
|
ScalesetState State,
|
||||||
string VmSku,
|
string VmSku,
|
||||||
ImageReference Image,
|
ImageReference Image,
|
||||||
@ -425,7 +421,31 @@ public record Scaleset(
|
|||||||
Guid? ClientId = null,
|
Guid? ClientId = null,
|
||||||
Guid? ClientObjectId = null
|
Guid? ClientObjectId = null
|
||||||
// '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);
|
) : StatefulEntityBase<ScalesetState>(State) {
|
||||||
|
|
||||||
|
[GeneratedRegex(@"[^a-zA-Z0-9\-]+")]
|
||||||
|
private static partial Regex InvalidCharacterRegex();
|
||||||
|
|
||||||
|
public static ScalesetId GenerateNewScalesetId(PoolName poolName)
|
||||||
|
=> GenerateNewScalesetIdUsingGuid(poolName, Guid.NewGuid());
|
||||||
|
|
||||||
|
public static ScalesetId GenerateNewScalesetIdUsingGuid(PoolName poolName, Guid guid) {
|
||||||
|
// poolnames permit underscores but not scaleset names; use hyphen instead:
|
||||||
|
var name = poolName.ToString().Replace("_", "-");
|
||||||
|
|
||||||
|
// since poolnames are not actually validated, take only the valid characters:
|
||||||
|
name = InvalidCharacterRegex().Replace(name, "");
|
||||||
|
|
||||||
|
// trim off any starting and ending dashes:
|
||||||
|
name = name.Trim('-');
|
||||||
|
|
||||||
|
// this should now be a valid name; generate a unique suffix:
|
||||||
|
// max length is 64; length of Guid in "N" format is 32, -1 for the hyphen
|
||||||
|
name = name[..Math.Min(64 - 32 - 1, name.Length)] + "-" + guid.ToString("N");
|
||||||
|
|
||||||
|
return ScalesetId.Parse(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public record Notification(
|
public record Notification(
|
||||||
[PartitionKey] Guid NotificationId,
|
[PartitionKey] Guid NotificationId,
|
||||||
@ -733,7 +753,7 @@ public record WorkSetSummary(
|
|||||||
);
|
);
|
||||||
|
|
||||||
public record ScalesetSummary(
|
public record ScalesetSummary(
|
||||||
Guid ScalesetId,
|
ScalesetId ScalesetId,
|
||||||
ScalesetState State
|
ScalesetState State
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ public record NodeUpdate(
|
|||||||
public record NodeSearch(
|
public record NodeSearch(
|
||||||
Guid? MachineId = null,
|
Guid? MachineId = null,
|
||||||
List<NodeState>? State = null,
|
List<NodeState>? State = null,
|
||||||
Guid? ScalesetId = null,
|
ScalesetId? ScalesetId = null,
|
||||||
PoolName? PoolName = null
|
PoolName? PoolName = null
|
||||||
) : BaseRequest;
|
) : BaseRequest;
|
||||||
|
|
||||||
@ -172,20 +172,20 @@ public record ReproCreate(
|
|||||||
) : BaseRequest;
|
) : BaseRequest;
|
||||||
|
|
||||||
public record ProxyGet(
|
public record ProxyGet(
|
||||||
Guid? ScalesetId,
|
ScalesetId? ScalesetId,
|
||||||
Guid? MachineId,
|
Guid? MachineId,
|
||||||
int? DstPort
|
int? DstPort
|
||||||
) : BaseRequest;
|
) : BaseRequest;
|
||||||
|
|
||||||
public record ProxyCreate(
|
public record ProxyCreate(
|
||||||
[property: Required] Guid ScalesetId,
|
[property: Required] ScalesetId ScalesetId,
|
||||||
[property: Required] Guid MachineId,
|
[property: Required] Guid MachineId,
|
||||||
[property: Required] int DstPort,
|
[property: Required] int DstPort,
|
||||||
[property: Required] int Duration
|
[property: Required] int Duration
|
||||||
) : BaseRequest;
|
) : BaseRequest;
|
||||||
|
|
||||||
public record ProxyDelete(
|
public record ProxyDelete(
|
||||||
[property: Required] Guid ScalesetId,
|
[property: Required] ScalesetId ScalesetId,
|
||||||
[property: Required] Guid MachineId,
|
[property: Required] Guid MachineId,
|
||||||
int? DstPort
|
int? DstPort
|
||||||
) : BaseRequest;
|
) : BaseRequest;
|
||||||
@ -217,18 +217,18 @@ public record AutoScaleOptions(
|
|||||||
);
|
);
|
||||||
|
|
||||||
public record ScalesetSearch(
|
public record ScalesetSearch(
|
||||||
Guid? ScalesetId = null,
|
ScalesetId? ScalesetId = null,
|
||||||
List<ScalesetState>? State = null,
|
List<ScalesetState>? State = null,
|
||||||
bool IncludeAuth = false
|
bool IncludeAuth = false
|
||||||
) : BaseRequest;
|
) : BaseRequest;
|
||||||
|
|
||||||
public record ScalesetStop(
|
public record ScalesetStop(
|
||||||
[property: Required] Guid ScalesetId,
|
[property: Required] ScalesetId ScalesetId,
|
||||||
[property: Required] bool Now
|
[property: Required] bool Now
|
||||||
) : BaseRequest;
|
) : BaseRequest;
|
||||||
|
|
||||||
public record ScalesetUpdate(
|
public record ScalesetUpdate(
|
||||||
[property: Required] Guid ScalesetId,
|
[property: Required] ScalesetId ScalesetId,
|
||||||
[property: Range(1, long.MaxValue)]
|
[property: Range(1, long.MaxValue)]
|
||||||
long? Size
|
long? Size
|
||||||
) : BaseRequest;
|
) : BaseRequest;
|
||||||
@ -313,7 +313,7 @@ public record AgentRegistrationGet(
|
|||||||
|
|
||||||
public record AgentRegistrationPost(
|
public record AgentRegistrationPost(
|
||||||
[property: Required] PoolName PoolName,
|
[property: Required] PoolName PoolName,
|
||||||
Guid? ScalesetId,
|
ScalesetId? ScalesetId,
|
||||||
[property: Required] Guid MachineId,
|
[property: Required] Guid MachineId,
|
||||||
Os? Os,
|
Os? Os,
|
||||||
string? MachineName,
|
string? MachineName,
|
||||||
|
@ -30,7 +30,7 @@ public record NodeSearchResult(
|
|||||||
DateTimeOffset? Heartbeat,
|
DateTimeOffset? Heartbeat,
|
||||||
DateTimeOffset? InitializedAt,
|
DateTimeOffset? InitializedAt,
|
||||||
NodeState State,
|
NodeState State,
|
||||||
Guid? ScalesetId,
|
ScalesetId? ScalesetId,
|
||||||
bool ReimageRequested,
|
bool ReimageRequested,
|
||||||
bool DeleteRequested,
|
bool DeleteRequested,
|
||||||
bool DebugKeepNode
|
bool DebugKeepNode
|
||||||
@ -123,7 +123,7 @@ public record PoolGetResult(
|
|||||||
|
|
||||||
public record ScalesetResponse(
|
public record ScalesetResponse(
|
||||||
PoolName PoolName,
|
PoolName PoolName,
|
||||||
Guid ScalesetId,
|
ScalesetId ScalesetId,
|
||||||
ScalesetState State,
|
ScalesetState State,
|
||||||
Authentication? Auth,
|
Authentication? Auth,
|
||||||
string VmSku,
|
string VmSku,
|
||||||
@ -159,6 +159,12 @@ public record ScalesetResponse(
|
|||||||
Nodes: null);
|
Nodes: null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public record ScalesetNodeState(
|
||||||
|
Guid MachineId,
|
||||||
|
string? InstanceId,
|
||||||
|
NodeState? State
|
||||||
|
);
|
||||||
|
|
||||||
public record ConfigResponse(
|
public record ConfigResponse(
|
||||||
string? Authority,
|
string? Authority,
|
||||||
string? ClientId,
|
string? ClientId,
|
||||||
|
@ -16,9 +16,15 @@ static partial class Check {
|
|||||||
public static bool IsAlnumDash(string input) => IsAlnumDashRegex().IsMatch(input);
|
public static bool IsAlnumDash(string input) => IsAlnumDashRegex().IsMatch(input);
|
||||||
|
|
||||||
// Permits 1-64 characters: alphanumeric, underscore, period, or dash.
|
// Permits 1-64 characters: alphanumeric, underscore, period, or dash.
|
||||||
[GeneratedRegex("\\A[._a-zA-Z0-9\\-]{1,64}\\z")]
|
// Cannot start with underscore (or dash) or end with period or dash.
|
||||||
private static partial Regex IsNameLikeRegex();
|
[GeneratedRegex(@"\A(?![_\-])[._a-zA-Z0-9\-]{1,64}(?<![.\-])\z")]
|
||||||
public static bool IsNameLike(string input) => IsNameLikeRegex().IsMatch(input);
|
private static partial Regex ResourceNameRegex();
|
||||||
|
public static bool IsResourceName(string input) => ResourceNameRegex().IsMatch(input);
|
||||||
|
|
||||||
|
// The same as ResourceNameRegex but underscore and period are not permitted.
|
||||||
|
[GeneratedRegex(@"\A(?!-)[a-zA-Z0-9\-]{1,64}(?<!-)\z")]
|
||||||
|
private static partial Regex VmssNameRegex();
|
||||||
|
public static bool IsVmssName(string input) => VmssNameRegex().IsMatch(input);
|
||||||
|
|
||||||
// This regex is based upon DNS labels but more restricted.
|
// This regex is based upon DNS labels but more restricted.
|
||||||
// It is used for many different Storage resources.
|
// It is used for many different Storage resources.
|
||||||
@ -31,13 +37,16 @@ static partial class Check {
|
|||||||
public static bool IsStorageDnsLabel(string input) => StorageDnsLabelRegex().IsMatch(input);
|
public static bool IsStorageDnsLabel(string input) => StorageDnsLabelRegex().IsMatch(input);
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface IValidatedString<T> where T : IValidatedString<T> {
|
public interface IValidatedString {
|
||||||
public static abstract T Parse(string input);
|
|
||||||
public static abstract bool IsValid(string input);
|
public static abstract bool IsValid(string input);
|
||||||
public static abstract string Requirements { get; }
|
public static abstract string Requirements { get; }
|
||||||
public string String { get; }
|
public string String { get; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public interface IValidatedString<T> : IValidatedString where T : IValidatedString<T> {
|
||||||
|
public static abstract T Parse(string input);
|
||||||
|
}
|
||||||
|
|
||||||
public abstract record ValidatedStringBase<T> where T : IValidatedString<T> {
|
public abstract record ValidatedStringBase<T> where T : IValidatedString<T> {
|
||||||
protected ValidatedStringBase(string value) {
|
protected ValidatedStringBase(string value) {
|
||||||
if (!T.IsValid(value)) {
|
if (!T.IsValid(value)) {
|
||||||
@ -109,3 +118,11 @@ public sealed record Container : ValidatedStringBase<Container>, IValidatedStrin
|
|||||||
public static bool IsValid(string input) => Check.IsStorageDnsLabel(input);
|
public static bool IsValid(string input) => Check.IsStorageDnsLabel(input);
|
||||||
public static string Requirements => "Container name must be 3-63 lowercase letters, numbers, or non-consecutive hyphens (see: https://docs.microsoft.com/en-us/azure/azure-resource-manager/management/resource-name-rules#microsoftstorage)";
|
public static string Requirements => "Container name must be 3-63 lowercase letters, numbers, or non-consecutive hyphens (see: https://docs.microsoft.com/en-us/azure/azure-resource-manager/management/resource-name-rules#microsoftstorage)";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[JsonConverter(typeof(ValidatedStringConverter<ScalesetId>))]
|
||||||
|
public sealed record ScalesetId : ValidatedStringBase<ScalesetId>, IValidatedString<ScalesetId> {
|
||||||
|
private ScalesetId(string value) : base(value) { }
|
||||||
|
public static ScalesetId Parse(string input) => new(input);
|
||||||
|
public static bool IsValid(string input) => Check.IsVmssName(input);
|
||||||
|
public static string Requirements => "Virtual machine scaleset names must be 1-64 numbers, letters, or dashes (not at start or end).";
|
||||||
|
}
|
||||||
|
@ -159,7 +159,10 @@ namespace ApiService.TestHooks {
|
|||||||
|
|
||||||
var query = UriExtension.GetQueryComponents(req.Url);
|
var query = UriExtension.GetQueryComponents(req.Url);
|
||||||
Guid? poolId = UriExtension.GetGuid("poolId", query);
|
Guid? poolId = UriExtension.GetGuid("poolId", query);
|
||||||
Guid? scaleSetId = UriExtension.GetGuid("scaleSetId", query);
|
var scaleSetId = UriExtension.GetString("scaleSetId", query)
|
||||||
|
is string scalesetId
|
||||||
|
? ScalesetId.Parse(scalesetId)
|
||||||
|
: null;
|
||||||
|
|
||||||
List<NodeState>? states = default;
|
List<NodeState>? states = default;
|
||||||
if (query.TryGetValue("states", out var value)) {
|
if (query.TryGetValue("states", out var value)) {
|
||||||
@ -196,7 +199,7 @@ namespace ApiService.TestHooks {
|
|||||||
_log.Info($"reimage long lived nodes");
|
_log.Info($"reimage long lived nodes");
|
||||||
var query = UriExtension.GetQueryComponents(req.Url);
|
var query = UriExtension.GetQueryComponents(req.Url);
|
||||||
|
|
||||||
var r = _nodeOps.ReimageLongLivedNodes(Guid.Parse(query["scaleSetId"]));
|
var r = _nodeOps.ReimageLongLivedNodes(ScalesetId.Parse(query["scaleSetId"]));
|
||||||
var resp = req.CreateResponse(HttpStatusCode.OK);
|
var resp = req.CreateResponse(HttpStatusCode.OK);
|
||||||
await resp.WriteAsJsonAsync(r);
|
await resp.WriteAsJsonAsync(r);
|
||||||
return resp;
|
return resp;
|
||||||
@ -213,9 +216,9 @@ namespace ApiService.TestHooks {
|
|||||||
var poolName = PoolName.Parse(query["poolName"]);
|
var poolName = PoolName.Parse(query["poolName"]);
|
||||||
Guid machineId = Guid.Parse(query["machineId"]);
|
Guid machineId = Guid.Parse(query["machineId"]);
|
||||||
|
|
||||||
Guid? scaleSetId = default;
|
ScalesetId? scaleSetId = null;
|
||||||
if (query.TryGetValue("scaleSetId", out var value)) {
|
if (query.TryGetValue("scaleSetId", out var value)) {
|
||||||
scaleSetId = Guid.Parse(value);
|
scaleSetId = ScalesetId.Parse(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
string version = query["version"];
|
string version = query["version"];
|
||||||
@ -236,10 +239,10 @@ namespace ApiService.TestHooks {
|
|||||||
|
|
||||||
var query = UriExtension.GetQueryComponents(req.Url);
|
var query = UriExtension.GetQueryComponents(req.Url);
|
||||||
|
|
||||||
Guid scaleSetId = Guid.Parse(query["scaleSetId"]);
|
var scaleSetId = ScalesetId.Parse(query["scaleSetId"]);
|
||||||
TimeSpan timeSpan = TimeSpan.Parse(query["timeSpan"]);
|
TimeSpan timeSpan = TimeSpan.Parse(query["timeSpan"]);
|
||||||
|
|
||||||
var nodes = await (_nodeOps.GetDeadNodes(scaleSetId, timeSpan).ToListAsync());
|
var nodes = await _nodeOps.GetDeadNodes(scaleSetId, timeSpan).ToListAsync();
|
||||||
var json = JsonSerializer.Serialize(nodes, EntityConverter.GetJsonSerializerOptions());
|
var json = JsonSerializer.Serialize(nodes, EntityConverter.GetJsonSerializerOptions());
|
||||||
var resp = req.CreateResponse(HttpStatusCode.OK);
|
var resp = req.CreateResponse(HttpStatusCode.OK);
|
||||||
await resp.WriteStringAsync(json);
|
await resp.WriteStringAsync(json);
|
||||||
|
@ -27,7 +27,7 @@ namespace ApiService.TestHooks {
|
|||||||
var query = UriExtension.GetQueryComponents(req.Url);
|
var query = UriExtension.GetQueryComponents(req.Url);
|
||||||
|
|
||||||
var poolRes = _proxyForward.SearchForward(
|
var poolRes = _proxyForward.SearchForward(
|
||||||
UriExtension.GetGuid("scaleSetId", query),
|
UriExtension.GetString("scaleSetId", query) is string scalesetId ? ScalesetId.Parse(scalesetId) : null,
|
||||||
UriExtension.GetString("region", query) is string region ? Region.Parse(region) : null,
|
UriExtension.GetString("region", query) is string region ? Region.Parse(region) : null,
|
||||||
UriExtension.GetGuid("machineId", query),
|
UriExtension.GetGuid("machineId", query),
|
||||||
UriExtension.GetGuid("proxyId", query),
|
UriExtension.GetGuid("proxyId", query),
|
||||||
|
@ -27,8 +27,8 @@ namespace ApiService.TestHooks {
|
|||||||
public async Task<HttpResponseData> ListInstanceIds([HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "testhooks/vmssOperations/listInstanceIds")] HttpRequestData req) {
|
public async Task<HttpResponseData> ListInstanceIds([HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "testhooks/vmssOperations/listInstanceIds")] HttpRequestData req) {
|
||||||
_log.Info($"list instance ids");
|
_log.Info($"list instance ids");
|
||||||
var query = UriExtension.GetQueryComponents(req.Url);
|
var query = UriExtension.GetQueryComponents(req.Url);
|
||||||
var name = UriExtension.GetGuid("name", query) ?? throw new Exception("name must be set");
|
var name = UriExtension.GetString("name", query) ?? throw new Exception("name must be set");
|
||||||
var ids = await _vmssOps.ListInstanceIds(name);
|
var ids = await _vmssOps.ListInstanceIds(ScalesetId.Parse(name));
|
||||||
|
|
||||||
var json = JsonSerializer.Serialize(ids, EntityConverter.GetJsonSerializerOptions());
|
var json = JsonSerializer.Serialize(ids, EntityConverter.GetJsonSerializerOptions());
|
||||||
var resp = req.CreateResponse(HttpStatusCode.OK);
|
var resp = req.CreateResponse(HttpStatusCode.OK);
|
||||||
@ -40,9 +40,9 @@ namespace ApiService.TestHooks {
|
|||||||
public async Task<HttpResponseData> GetInstanceId([HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "testhooks/vmssOperations/getInstanceId")] HttpRequestData req) {
|
public async Task<HttpResponseData> GetInstanceId([HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "testhooks/vmssOperations/getInstanceId")] HttpRequestData req) {
|
||||||
_log.Info($"list instance ids");
|
_log.Info($"list instance ids");
|
||||||
var query = UriExtension.GetQueryComponents(req.Url);
|
var query = UriExtension.GetQueryComponents(req.Url);
|
||||||
var name = UriExtension.GetGuid("name", query) ?? throw new Exception("name must be set");
|
var name = UriExtension.GetString("name", query) ?? throw new Exception("name must be set");
|
||||||
var vmId = UriExtension.GetGuid("vmId", query) ?? throw new Exception("vmId must be set");
|
var vmId = UriExtension.GetGuid("vmId", query) ?? throw new Exception("vmId must be set");
|
||||||
var id = await _vmssOps.GetInstanceId(name, vmId);
|
var id = await _vmssOps.GetInstanceId(ScalesetId.Parse(name), vmId);
|
||||||
|
|
||||||
var json = JsonSerializer.Serialize(id, EntityConverter.GetJsonSerializerOptions());
|
var json = JsonSerializer.Serialize(id, EntityConverter.GetJsonSerializerOptions());
|
||||||
var resp = req.CreateResponse(HttpStatusCode.OK);
|
var resp = req.CreateResponse(HttpStatusCode.OK);
|
||||||
@ -54,9 +54,9 @@ namespace ApiService.TestHooks {
|
|||||||
public async Task<HttpResponseData> UpdateScaleInProtection([HttpTrigger(AuthorizationLevel.Anonymous, "put", Route = "testhooks/vmssOperations/updateScaleInProtection")] HttpRequestData req) {
|
public async Task<HttpResponseData> UpdateScaleInProtection([HttpTrigger(AuthorizationLevel.Anonymous, "put", Route = "testhooks/vmssOperations/updateScaleInProtection")] HttpRequestData req) {
|
||||||
_log.Info($"list instance ids");
|
_log.Info($"list instance ids");
|
||||||
var query = UriExtension.GetQueryComponents(req.Url);
|
var query = UriExtension.GetQueryComponents(req.Url);
|
||||||
var name = UriExtension.GetGuid("name", query) ?? throw new Exception("name must be set");
|
var name = UriExtension.GetString("name", query) ?? throw new Exception("name must be set");
|
||||||
var instanceId = UriExtension.GetString("instanceId", query) ?? throw new Exception("instanceId must be set");
|
var instanceId = UriExtension.GetString("instanceId", query) ?? throw new Exception("instanceId must be set");
|
||||||
var scalesetResult = await _scalesetOperations.GetById(name);
|
var scalesetResult = await _scalesetOperations.GetById(ScalesetId.Parse(name));
|
||||||
if (!scalesetResult.IsOk) {
|
if (!scalesetResult.IsOk) {
|
||||||
throw new Exception("invalid scaleset name");
|
throw new Exception("invalid scaleset name");
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ public interface IAutoScaleOperations {
|
|||||||
|
|
||||||
public Async.Task<ResultVoid<(HttpStatusCode Status, string Reason)>> Insert(AutoScale autoScale);
|
public Async.Task<ResultVoid<(HttpStatusCode Status, string Reason)>> Insert(AutoScale autoScale);
|
||||||
|
|
||||||
public Async.Task<AutoScale?> GetSettingsForScaleset(Guid scalesetId);
|
public Async.Task<AutoScale?> GetSettingsForScaleset(ScalesetId scalesetId);
|
||||||
|
|
||||||
AutoscaleProfile CreateAutoScaleProfile(
|
AutoscaleProfile CreateAutoScaleProfile(
|
||||||
string queueUri,
|
string queueUri,
|
||||||
@ -26,16 +26,16 @@ public interface IAutoScaleOperations {
|
|||||||
double scaleInCooldownMinutes);
|
double scaleInCooldownMinutes);
|
||||||
|
|
||||||
AutoscaleProfile DefaultAutoScaleProfile(string queueUri, long scaleSetSize);
|
AutoscaleProfile DefaultAutoScaleProfile(string queueUri, long scaleSetSize);
|
||||||
Async.Task<OneFuzzResultVoid> AddAutoScaleToVmss(Guid vmss, AutoscaleProfile autoScaleProfile);
|
Async.Task<OneFuzzResultVoid> AddAutoScaleToVmss(ScalesetId vmss, AutoscaleProfile autoScaleProfile);
|
||||||
|
|
||||||
OneFuzzResult<AutoscaleSettingResource?> GetAutoscaleSettings(Guid vmss);
|
OneFuzzResult<AutoscaleSettingResource?> GetAutoscaleSettings(ScalesetId vmss);
|
||||||
|
|
||||||
Async.Task<OneFuzzResultVoid> UpdateAutoscale(AutoscaleSettingData autoscale);
|
Async.Task<OneFuzzResultVoid> UpdateAutoscale(AutoscaleSettingData autoscale);
|
||||||
|
|
||||||
Async.Task<OneFuzzResult<AutoscaleProfile>> GetAutoScaleProfile(Guid scalesetId);
|
Async.Task<OneFuzzResult<AutoscaleProfile>> GetAutoScaleProfile(ScalesetId scalesetId);
|
||||||
|
|
||||||
Async.Task<AutoScale> Update(
|
Async.Task<AutoScale> Update(
|
||||||
Guid scalesetId,
|
ScalesetId scalesetId,
|
||||||
long minAmount,
|
long minAmount,
|
||||||
long maxAmount,
|
long maxAmount,
|
||||||
long defaultAmount,
|
long defaultAmount,
|
||||||
@ -54,7 +54,7 @@ public class AutoScaleOperations : Orm<AutoScale>, IAutoScaleOperations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async Async.Task<AutoScale> Create(
|
public async Async.Task<AutoScale> Create(
|
||||||
Guid scalesetId,
|
ScalesetId scalesetId,
|
||||||
long minAmount,
|
long minAmount,
|
||||||
long maxAmount,
|
long maxAmount,
|
||||||
long defaultAmount,
|
long defaultAmount,
|
||||||
@ -81,7 +81,7 @@ public class AutoScaleOperations : Orm<AutoScale>, IAutoScaleOperations {
|
|||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Async.Task<AutoScale?> GetSettingsForScaleset(Guid scalesetId) {
|
public async Async.Task<AutoScale?> GetSettingsForScaleset(ScalesetId scalesetId) {
|
||||||
try {
|
try {
|
||||||
var autoscale = await GetEntityAsync(scalesetId.ToString(), scalesetId.ToString());
|
var autoscale = await GetEntityAsync(scalesetId.ToString(), scalesetId.ToString());
|
||||||
return autoscale;
|
return autoscale;
|
||||||
@ -91,7 +91,7 @@ public class AutoScaleOperations : Orm<AutoScale>, IAutoScaleOperations {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Async.Task<OneFuzzResult<AutoscaleProfile>> GetAutoScaleProfile(Guid scalesetId) {
|
public async Async.Task<OneFuzzResult<AutoscaleProfile>> GetAutoScaleProfile(ScalesetId scalesetId) {
|
||||||
_logTracer.Info($"getting scaleset for existing auto-scale resources {scalesetId:Tag:ScalesetId}");
|
_logTracer.Info($"getting scaleset for existing auto-scale resources {scalesetId:Tag:ScalesetId}");
|
||||||
var settings = _context.Creds.GetResourceGroupResource().GetAutoscaleSettings();
|
var settings = _context.Creds.GetResourceGroupResource().GetAutoscaleSettings();
|
||||||
if (settings is null) {
|
if (settings is null) {
|
||||||
@ -114,7 +114,7 @@ public class AutoScaleOperations : Orm<AutoScale>, IAutoScaleOperations {
|
|||||||
return OneFuzzResult<AutoscaleProfile>.Error(ErrorCode.INVALID_CONFIGURATION, $"could not find auto-scale settings for scaleset {scalesetId}");
|
return OneFuzzResult<AutoscaleProfile>.Error(ErrorCode.INVALID_CONFIGURATION, $"could not find auto-scale settings for scaleset {scalesetId}");
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Async.Task<OneFuzzResultVoid> AddAutoScaleToVmss(Guid vmss, AutoscaleProfile autoScaleProfile) {
|
public async Async.Task<OneFuzzResultVoid> AddAutoScaleToVmss(ScalesetId vmss, AutoscaleProfile autoScaleProfile) {
|
||||||
_logTracer.Info($"Checking scaleset {vmss:Tag:ScalesetId} for existing auto scale resource");
|
_logTracer.Info($"Checking scaleset {vmss:Tag:ScalesetId} for existing auto scale resource");
|
||||||
|
|
||||||
var existingAutoScaleResource = GetAutoscaleSettings(vmss);
|
var existingAutoScaleResource = GetAutoscaleSettings(vmss);
|
||||||
@ -141,7 +141,7 @@ public class AutoScaleOperations : Orm<AutoScale>, IAutoScaleOperations {
|
|||||||
|
|
||||||
return OneFuzzResultVoid.Ok;
|
return OneFuzzResultVoid.Ok;
|
||||||
}
|
}
|
||||||
private async Async.Task<OneFuzzResult<AutoscaleSettingResource>> CreateAutoScaleResourceFor(Guid resourceId, Region location, AutoscaleProfile profile) {
|
private async Async.Task<OneFuzzResult<AutoscaleSettingResource>> CreateAutoScaleResourceFor(ScalesetId resourceId, Region location, AutoscaleProfile profile) {
|
||||||
_logTracer.Info($"Creating auto-scale resource for: {resourceId:Tag:AutoscaleResourceId}");
|
_logTracer.Info($"Creating auto-scale resource for: {resourceId:Tag:AutoscaleResourceId}");
|
||||||
|
|
||||||
var resourceGroup = _context.Creds.GetBaseResourceGroup();
|
var resourceGroup = _context.Creds.GetBaseResourceGroup();
|
||||||
@ -287,7 +287,7 @@ public class AutoScaleOperations : Orm<AutoScale>, IAutoScaleOperations {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public OneFuzzResult<AutoscaleSettingResource?> GetAutoscaleSettings(Guid vmss) {
|
public OneFuzzResult<AutoscaleSettingResource?> GetAutoscaleSettings(ScalesetId vmss) {
|
||||||
_logTracer.Info($"Checking scaleset {vmss:Tag:ScalesetId} for existing auto scale resource");
|
_logTracer.Info($"Checking scaleset {vmss:Tag:ScalesetId} for existing auto scale resource");
|
||||||
try {
|
try {
|
||||||
var autoscale = _context.Creds.GetResourceGroupResource().GetAutoscaleSettings()
|
var autoscale = _context.Creds.GetResourceGroupResource().GetAutoscaleSettings()
|
||||||
@ -354,7 +354,7 @@ public class AutoScaleOperations : Orm<AutoScale>, IAutoScaleOperations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async Async.Task<AutoScale> Update(
|
public async Async.Task<AutoScale> Update(
|
||||||
Guid scalesetId,
|
ScalesetId scalesetId,
|
||||||
long minAmount,
|
long minAmount,
|
||||||
long maxAmount,
|
long maxAmount,
|
||||||
long defaultAmount,
|
long defaultAmount,
|
||||||
|
@ -24,16 +24,15 @@ public interface IIpOperations {
|
|||||||
|
|
||||||
public Async.Task DeleteIp(string resourceGroup, string name);
|
public Async.Task DeleteIp(string resourceGroup, string name);
|
||||||
|
|
||||||
public Async.Task<string?> GetScalesetInstanceIp(Guid scalesetId, Guid machineId);
|
public Async.Task<string?> GetScalesetInstanceIp(ScalesetId scalesetId, Guid machineId);
|
||||||
|
|
||||||
public Async.Task CreateIp(string resourceGroup, string name, Region region);
|
public Async.Task CreateIp(string resourceGroup, string name, Region region);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public class IpOperations : IIpOperations {
|
public class IpOperations : IIpOperations {
|
||||||
private ILogTracer _logTracer;
|
private readonly ILogTracer _logTracer;
|
||||||
|
private readonly IOnefuzzContext _context;
|
||||||
private IOnefuzzContext _context;
|
|
||||||
private readonly NetworkInterfaceQuery _networkInterfaceQuery;
|
private readonly NetworkInterfaceQuery _networkInterfaceQuery;
|
||||||
|
|
||||||
public IpOperations(ILogTracer log, IOnefuzzContext context) {
|
public IpOperations(ILogTracer log, IOnefuzzContext context) {
|
||||||
@ -86,7 +85,7 @@ public class IpOperations : IIpOperations {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string?> GetScalesetInstanceIp(Guid scalesetId, Guid machineId) {
|
public async Task<string?> GetScalesetInstanceIp(ScalesetId scalesetId, Guid machineId) {
|
||||||
var instance = await _context.VmssOperations.GetInstanceId(scalesetId, machineId);
|
var instance = await _context.VmssOperations.GetInstanceId(scalesetId, machineId);
|
||||||
if (!instance.IsOk) {
|
if (!instance.IsOk) {
|
||||||
_logTracer.Verbose($"failed to get vmss {scalesetId:Tag:ScalesetId} for instance id {machineId:Tag:MachineId} due to {instance.ErrorV:Tag:Error}");
|
_logTracer.Verbose($"failed to get vmss {scalesetId:Tag:ScalesetId} for instance id {machineId:Tag:MachineId} due to {instance.ErrorV:Tag:Error}");
|
||||||
@ -253,7 +252,7 @@ public class IpOperations : IIpOperations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public async Task<List<string>> ListInstancePrivateIps(Guid scalesetId, string instanceId) {
|
public async Task<List<string>> ListInstancePrivateIps(ScalesetId scalesetId, string instanceId) {
|
||||||
var token = _context.Creds.GetIdentity().GetToken(
|
var token = _context.Creds.GetIdentity().GetToken(
|
||||||
new TokenRequestContext(
|
new TokenRequestContext(
|
||||||
new[] { $"https://management.azure.com" }));
|
new[] { $"https://management.azure.com" }));
|
||||||
|
@ -27,7 +27,7 @@ public interface INodeOperations : IStatefulOrm<Node, NodeState> {
|
|||||||
Async.Task<Node> ToReimage(Node node, bool done = false);
|
Async.Task<Node> ToReimage(Node node, bool done = false);
|
||||||
Async.Task SendStopIfFree(Node node);
|
Async.Task SendStopIfFree(Node node);
|
||||||
IAsyncEnumerable<Node> SearchStates(Guid? poolId = default,
|
IAsyncEnumerable<Node> SearchStates(Guid? poolId = default,
|
||||||
Guid? scalesetId = default,
|
ScalesetId? scalesetId = default,
|
||||||
IEnumerable<NodeState>? states = default,
|
IEnumerable<NodeState>? states = default,
|
||||||
PoolName? poolName = default,
|
PoolName? poolName = default,
|
||||||
bool excludeUpdateScheduled = false,
|
bool excludeUpdateScheduled = false,
|
||||||
@ -35,18 +35,18 @@ public interface INodeOperations : IStatefulOrm<Node, NodeState> {
|
|||||||
|
|
||||||
Async.Task Delete(Node node, string reason);
|
Async.Task Delete(Node node, string reason);
|
||||||
|
|
||||||
Async.Task ReimageLongLivedNodes(Guid scaleSetId);
|
Async.Task ReimageLongLivedNodes(ScalesetId scaleSetId);
|
||||||
|
|
||||||
Async.Task<Node?> Create(
|
Async.Task<Node?> Create(
|
||||||
Guid poolId,
|
Guid poolId,
|
||||||
PoolName poolName,
|
PoolName poolName,
|
||||||
Guid machineId,
|
Guid machineId,
|
||||||
string? instanceId,
|
string? instanceId,
|
||||||
Guid? scaleSetId,
|
ScalesetId? scaleSetId,
|
||||||
string version,
|
string version,
|
||||||
bool isNew = false);
|
bool isNew = false);
|
||||||
|
|
||||||
IAsyncEnumerable<Node> GetDeadNodes(Guid scaleSetId, TimeSpan expirationPeriod);
|
IAsyncEnumerable<Node> GetDeadNodes(ScalesetId scaleSetId, TimeSpan expirationPeriod);
|
||||||
|
|
||||||
Async.Task MarkTasksStoppedEarly(Node node, Error? error);
|
Async.Task MarkTasksStoppedEarly(Node node, Error? error);
|
||||||
static readonly TimeSpan NODE_EXPIRATION_TIME = TimeSpan.FromHours(1.0);
|
static readonly TimeSpan NODE_EXPIRATION_TIME = TimeSpan.FromHours(1.0);
|
||||||
@ -88,7 +88,7 @@ public class NodeOperations : StatefulOrm<Node, NodeState, NodeOperations>, INod
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async Task<OneFuzzResult<Node>> AcquireScaleInProtection(Node node) {
|
public async Task<OneFuzzResult<Node>> AcquireScaleInProtection(Node node) {
|
||||||
if (node.ScalesetId is Guid scalesetId &&
|
if (node.ScalesetId is ScalesetId scalesetId &&
|
||||||
await TryGetNodeInfo(node) is NodeInfo nodeInfo) {
|
await TryGetNodeInfo(node) is NodeInfo nodeInfo) {
|
||||||
|
|
||||||
_logTracer.Info($"Setting scale-in protection on node {node.MachineId:Tag:MachineId}");
|
_logTracer.Info($"Setting scale-in protection on node {node.MachineId:Tag:MachineId}");
|
||||||
@ -118,7 +118,7 @@ public class NodeOperations : StatefulOrm<Node, NodeState, NodeOperations>, INod
|
|||||||
|
|
||||||
public async Task<OneFuzzResultVoid> ReleaseScaleInProtection(Node node) {
|
public async Task<OneFuzzResultVoid> ReleaseScaleInProtection(Node node) {
|
||||||
if (!node.DebugKeepNode &&
|
if (!node.DebugKeepNode &&
|
||||||
node.ScalesetId is Guid scalesetId &&
|
node.ScalesetId is ScalesetId scalesetId &&
|
||||||
await TryGetNodeInfo(node) is NodeInfo nodeInfo) {
|
await TryGetNodeInfo(node) is NodeInfo nodeInfo) {
|
||||||
|
|
||||||
_logTracer.Info($"Removing scale-in protection on node {node.MachineId:Tag:MachineId}");
|
_logTracer.Info($"Removing scale-in protection on node {node.MachineId:Tag:MachineId}");
|
||||||
@ -150,7 +150,7 @@ public class NodeOperations : StatefulOrm<Node, NodeState, NodeOperations>, INod
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var scalesetResult = await _context.ScalesetOperations.GetById(scalesetId.Value);
|
var scalesetResult = await _context.ScalesetOperations.GetById(scalesetId);
|
||||||
if (!scalesetResult.IsOk || scalesetResult.OkV == null) {
|
if (!scalesetResult.IsOk || scalesetResult.OkV == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -205,8 +205,8 @@ public class NodeOperations : StatefulOrm<Node, NodeState, NodeOperations>, INod
|
|||||||
return CanProcessNewWorkResponse.NotAllowed("node is scheduled to shrink");
|
return CanProcessNewWorkResponse.NotAllowed("node is scheduled to shrink");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (node.ScalesetId != null) {
|
if (node.ScalesetId is not null) {
|
||||||
var scalesetResult = await _context.ScalesetOperations.GetById(node.ScalesetId.Value);
|
var scalesetResult = await _context.ScalesetOperations.GetById(node.ScalesetId);
|
||||||
if (!scalesetResult.IsOk) {
|
if (!scalesetResult.IsOk) {
|
||||||
return CanProcessNewWorkResponse.NotAllowed("invalid scaleset");
|
return CanProcessNewWorkResponse.NotAllowed("invalid scaleset");
|
||||||
}
|
}
|
||||||
@ -235,7 +235,7 @@ public class NodeOperations : StatefulOrm<Node, NodeState, NodeOperations>, INod
|
|||||||
/// This helps keep nodes on scalesets that use `latest` OS image SKUs
|
/// This helps keep nodes on scalesets that use `latest` OS image SKUs
|
||||||
/// reasonably up-to-date with OS patches without disrupting running
|
/// reasonably up-to-date with OS patches without disrupting running
|
||||||
/// fuzzing tasks with patch reboot cycles.
|
/// fuzzing tasks with patch reboot cycles.
|
||||||
public async Async.Task ReimageLongLivedNodes(Guid scaleSetId) {
|
public async Async.Task ReimageLongLivedNodes(ScalesetId scaleSetId) {
|
||||||
var timeFilter = Query.OlderThan("initialized_at", DateTimeOffset.UtcNow - INodeOperations.NODE_REIMAGE_TIME);
|
var timeFilter = Query.OlderThan("initialized_at", DateTimeOffset.UtcNow - INodeOperations.NODE_REIMAGE_TIME);
|
||||||
|
|
||||||
await foreach (var node in QueryAsync(Query.And(Query.CreateQueryFilter($"scaleset_id eq {scaleSetId}"), timeFilter))) {
|
await foreach (var node in QueryAsync(Query.And(Query.CreateQueryFilter($"scaleset_id eq {scaleSetId}"), timeFilter))) {
|
||||||
@ -270,7 +270,7 @@ public class NodeOperations : StatefulOrm<Node, NodeState, NodeOperations>, INod
|
|||||||
public static string SearchOutdatedQuery(
|
public static string SearchOutdatedQuery(
|
||||||
string oneFuzzVersion,
|
string oneFuzzVersion,
|
||||||
Guid? poolId = null,
|
Guid? poolId = null,
|
||||||
Guid? scalesetId = null,
|
ScalesetId? scalesetId = null,
|
||||||
IEnumerable<NodeState>? states = null,
|
IEnumerable<NodeState>? states = null,
|
||||||
PoolName? poolName = null,
|
PoolName? poolName = null,
|
||||||
bool excludeUpdateScheduled = false,
|
bool excludeUpdateScheduled = false,
|
||||||
@ -283,7 +283,7 @@ public class NodeOperations : StatefulOrm<Node, NodeState, NodeOperations>, INod
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (poolName is not null) {
|
if (poolName is not null) {
|
||||||
queryParts.Add(Query.CreateQueryFilter($"(pool_name eq {poolName.String})"));
|
queryParts.Add(Query.CreateQueryFilter($"(pool_name eq {poolName})"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (scalesetId is not null) {
|
if (scalesetId is not null) {
|
||||||
@ -310,7 +310,7 @@ public class NodeOperations : StatefulOrm<Node, NodeState, NodeOperations>, INod
|
|||||||
|
|
||||||
IAsyncEnumerable<Node> SearchOutdated(
|
IAsyncEnumerable<Node> SearchOutdated(
|
||||||
Guid? poolId = null,
|
Guid? poolId = null,
|
||||||
Guid? scalesetId = null,
|
ScalesetId? scalesetId = null,
|
||||||
IEnumerable<NodeState>? states = null,
|
IEnumerable<NodeState>? states = null,
|
||||||
PoolName? poolName = null,
|
PoolName? poolName = null,
|
||||||
bool excludeUpdateScheduled = false,
|
bool excludeUpdateScheduled = false,
|
||||||
@ -366,11 +366,11 @@ public class NodeOperations : StatefulOrm<Node, NodeState, NodeOperations>, INod
|
|||||||
return updatedNode;
|
return updatedNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IAsyncEnumerable<Node> GetDeadNodes(Guid scaleSetId, TimeSpan expirationPeriod) {
|
public IAsyncEnumerable<Node> GetDeadNodes(ScalesetId scaleSetId, TimeSpan expirationPeriod) {
|
||||||
var minDate = DateTimeOffset.UtcNow - expirationPeriod;
|
var minDate = DateTimeOffset.UtcNow - expirationPeriod;
|
||||||
|
|
||||||
var filter = $"heartbeat lt datetime'{minDate.ToString("o")}' or Timestamp lt datetime'{minDate.ToString("o")}'";
|
var filter = $"heartbeat lt datetime'{minDate.ToString("o")}' or Timestamp lt datetime'{minDate.ToString("o")}'";
|
||||||
var query = Query.And(filter, $"scaleset_id eq '{scaleSetId}'");
|
var query = Query.And(filter, Query.CreateQueryFilter($"scaleset_id eq {scaleSetId}"));
|
||||||
return QueryAsync(query);
|
return QueryAsync(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -380,7 +380,7 @@ public class NodeOperations : StatefulOrm<Node, NodeState, NodeOperations>, INod
|
|||||||
PoolName poolName,
|
PoolName poolName,
|
||||||
Guid machineId,
|
Guid machineId,
|
||||||
string? instanceId,
|
string? instanceId,
|
||||||
Guid? scaleSetId,
|
ScalesetId? scaleSetId,
|
||||||
string version,
|
string version,
|
||||||
bool isNew = false) {
|
bool isNew = false) {
|
||||||
|
|
||||||
@ -495,7 +495,7 @@ public class NodeOperations : StatefulOrm<Node, NodeState, NodeOperations>, INod
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> CouldShrinkScaleset(Node node) {
|
public async Task<bool> CouldShrinkScaleset(Node node) {
|
||||||
if (node.ScalesetId is Guid scalesetId) {
|
if (node.ScalesetId is ScalesetId scalesetId) {
|
||||||
var queue = new ShrinkQueue(scalesetId, _context.Queue, _logTracer);
|
var queue = new ShrinkQueue(scalesetId, _context.Queue, _logTracer);
|
||||||
if (await queue.ShouldShrink()) {
|
if (await queue.ShouldShrink()) {
|
||||||
return true;
|
return true;
|
||||||
@ -536,7 +536,7 @@ public class NodeOperations : StatefulOrm<Node, NodeState, NodeOperations>, INod
|
|||||||
|
|
||||||
public static string SearchStatesQuery(
|
public static string SearchStatesQuery(
|
||||||
Guid? poolId = default,
|
Guid? poolId = default,
|
||||||
Guid? scaleSetId = default,
|
ScalesetId? scaleSetId = default,
|
||||||
IEnumerable<NodeState>? states = default,
|
IEnumerable<NodeState>? states = default,
|
||||||
PoolName? poolName = default,
|
PoolName? poolName = default,
|
||||||
int? numResults = default) {
|
int? numResults = default) {
|
||||||
@ -544,15 +544,15 @@ public class NodeOperations : StatefulOrm<Node, NodeState, NodeOperations>, INod
|
|||||||
List<string> queryParts = new();
|
List<string> queryParts = new();
|
||||||
|
|
||||||
if (poolId is not null) {
|
if (poolId is not null) {
|
||||||
queryParts.Add($"(pool_id eq '{poolId}')");
|
queryParts.Add(Query.CreateQueryFilter($"(pool_id eq {poolId})"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (poolName is not null) {
|
if (poolName is not null) {
|
||||||
queryParts.Add($"(PartitionKey eq '{poolName.String}')");
|
queryParts.Add(Query.CreateQueryFilter($"(PartitionKey eq {poolName})"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (scaleSetId is not null) {
|
if (scaleSetId is not null) {
|
||||||
queryParts.Add($"(scaleset_id eq '{scaleSetId}')");
|
queryParts.Add(Query.CreateQueryFilter($"(scaleset_id eq {scaleSetId})"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (states is not null) {
|
if (states is not null) {
|
||||||
@ -566,7 +566,7 @@ public class NodeOperations : StatefulOrm<Node, NodeState, NodeOperations>, INod
|
|||||||
|
|
||||||
public IAsyncEnumerable<Node> SearchStates(
|
public IAsyncEnumerable<Node> SearchStates(
|
||||||
Guid? poolId = default,
|
Guid? poolId = default,
|
||||||
Guid? scalesetId = default,
|
ScalesetId? scalesetId = default,
|
||||||
IEnumerable<NodeState>? states = default,
|
IEnumerable<NodeState>? states = default,
|
||||||
PoolName? poolName = default,
|
PoolName? poolName = default,
|
||||||
bool excludeUpdateScheduled = false,
|
bool excludeUpdateScheduled = false,
|
||||||
|
@ -5,10 +5,10 @@ namespace Microsoft.OneFuzz.Service;
|
|||||||
|
|
||||||
|
|
||||||
public interface IProxyForwardOperations : IOrm<ProxyForward> {
|
public interface IProxyForwardOperations : IOrm<ProxyForward> {
|
||||||
IAsyncEnumerable<ProxyForward> SearchForward(Guid? scalesetId = null, Region? region = null, Guid? machineId = null, Guid? proxyId = null, int? dstPort = null);
|
IAsyncEnumerable<ProxyForward> SearchForward(ScalesetId? scalesetId = null, Region? region = null, Guid? machineId = null, Guid? proxyId = null, int? dstPort = null);
|
||||||
Forward ToForward(ProxyForward proxyForward);
|
Forward ToForward(ProxyForward proxyForward);
|
||||||
Task<OneFuzzResult<ProxyForward>> UpdateOrCreate(Region region, Guid scalesetId, Guid machineId, int dstPort, int duration);
|
Task<OneFuzzResult<ProxyForward>> UpdateOrCreate(Region region, ScalesetId scalesetId, Guid machineId, int dstPort, int duration);
|
||||||
Task<HashSet<Region>> RemoveForward(Guid scalesetId, Guid? machineId = null, int? dstPort = null, Guid? proxyId = null);
|
Task<HashSet<Region>> RemoveForward(ScalesetId scalesetId, Guid? machineId = null, int? dstPort = null, Guid? proxyId = null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -20,12 +20,12 @@ public class ProxyForwardOperations : Orm<ProxyForward>, IProxyForwardOperations
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public IAsyncEnumerable<ProxyForward> SearchForward(Guid? scalesetId = null, Region? region = null, Guid? machineId = null, Guid? proxyId = null, int? dstPort = null) {
|
public IAsyncEnumerable<ProxyForward> SearchForward(ScalesetId? scalesetId = null, Region? region = null, Guid? machineId = null, Guid? proxyId = null, int? dstPort = null) {
|
||||||
|
|
||||||
var conditions =
|
var conditions =
|
||||||
new[] {
|
new[] {
|
||||||
scalesetId is not null ? Query.CreateQueryFilter($"scaleset_id eq {scalesetId}") : null,
|
scalesetId is not null ? Query.CreateQueryFilter($"scaleset_id eq {scalesetId}") : null,
|
||||||
region is not null ? Query.CreateQueryFilter($"PartitionKey eq {region.String}") : null ,
|
region is not null ? Query.CreateQueryFilter($"PartitionKey eq {region}") : null ,
|
||||||
machineId is not null ? Query.CreateQueryFilter($"machine_id eq {machineId}") : null ,
|
machineId is not null ? Query.CreateQueryFilter($"machine_id eq {machineId}") : null ,
|
||||||
proxyId is not null ? Query.CreateQueryFilter($"proxy_id eq {proxyId}") : null ,
|
proxyId is not null ? Query.CreateQueryFilter($"proxy_id eq {proxyId}") : null ,
|
||||||
dstPort is not null ? Query.CreateQueryFilter($"dst_port eq {dstPort}") : null ,
|
dstPort is not null ? Query.CreateQueryFilter($"dst_port eq {dstPort}") : null ,
|
||||||
@ -39,7 +39,7 @@ public class ProxyForwardOperations : Orm<ProxyForward>, IProxyForwardOperations
|
|||||||
return new Forward(proxyForward.Port, proxyForward.DstPort, proxyForward.DstIp);
|
return new Forward(proxyForward.Port, proxyForward.DstPort, proxyForward.DstIp);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<OneFuzzResult<ProxyForward>> UpdateOrCreate(Region region, Guid scalesetId, Guid machineId, int dstPort, int duration) {
|
public async Task<OneFuzzResult<ProxyForward>> UpdateOrCreate(Region region, ScalesetId scalesetId, Guid machineId, int dstPort, int duration) {
|
||||||
var privateIp = await _context.IpOperations.GetScalesetInstanceIp(scalesetId, machineId);
|
var privateIp = await _context.IpOperations.GetScalesetInstanceIp(scalesetId, machineId);
|
||||||
|
|
||||||
if (privateIp == null) {
|
if (privateIp == null) {
|
||||||
@ -87,7 +87,7 @@ public class ProxyForwardOperations : Orm<ProxyForward>, IProxyForwardOperations
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<HashSet<Region>> RemoveForward(Guid scalesetId, Guid? machineId, int? dstPort, Guid? proxyId) {
|
public async Task<HashSet<Region>> RemoveForward(ScalesetId scalesetId, Guid? machineId, int? dstPort, Guid? proxyId) {
|
||||||
var entries = await SearchForward(scalesetId: scalesetId, machineId: machineId, proxyId: proxyId, dstPort: dstPort).ToListAsync();
|
var entries = await SearchForward(scalesetId: scalesetId, machineId: machineId, proxyId: proxyId, dstPort: dstPort).ToListAsync();
|
||||||
|
|
||||||
var regions = new HashSet<Region>();
|
var regions = new HashSet<Region>();
|
||||||
|
@ -15,7 +15,7 @@ public interface IScalesetOperations : IStatefulOrm<Scaleset, ScalesetState> {
|
|||||||
|
|
||||||
Async.Task<Scaleset> UpdateConfigs(Scaleset scaleSet);
|
Async.Task<Scaleset> UpdateConfigs(Scaleset scaleSet);
|
||||||
|
|
||||||
Async.Task<OneFuzzResult<Scaleset>> GetById(Guid scalesetId);
|
Async.Task<OneFuzzResult<Scaleset>> GetById(ScalesetId scalesetId);
|
||||||
IAsyncEnumerable<Scaleset> GetByObjectId(Guid objectId);
|
IAsyncEnumerable<Scaleset> GetByObjectId(Guid objectId);
|
||||||
|
|
||||||
Async.Task<(bool, Scaleset)> CleanupNodes(Scaleset scaleSet);
|
Async.Task<(bool, Scaleset)> CleanupNodes(Scaleset scaleSet);
|
||||||
@ -584,7 +584,7 @@ public class ScalesetOperations : StatefulOrm<Scaleset, ScalesetState, ScalesetO
|
|||||||
} else {
|
} else {
|
||||||
if (await new ShrinkQueue(scaleSet.ScalesetId, _context.Queue, _log).ShouldShrink()) {
|
if (await new ShrinkQueue(scaleSet.ScalesetId, _context.Queue, _log).ShouldShrink()) {
|
||||||
toDelete[node.MachineId] = await _context.NodeOperations.SetHalt(node);
|
toDelete[node.MachineId] = await _context.NodeOperations.SetHalt(node);
|
||||||
} else if (await new ShrinkQueue(pool.OkV!.PoolId, _context.Queue, _log).ShouldShrink()) {
|
} else if (await new ShrinkQueue(pool.OkV.PoolId, _context.Queue, _log).ShouldShrink()) {
|
||||||
toDelete[node.MachineId] = await _context.NodeOperations.SetHalt(node);
|
toDelete[node.MachineId] = await _context.NodeOperations.SetHalt(node);
|
||||||
} else {
|
} else {
|
||||||
_logTracer.Info($"Node ready to reimage {node.MachineId:Tag:MachineId} {node.ScalesetId:Tag:ScalesetId} {node.State:Tag:State}");
|
_logTracer.Info($"Node ready to reimage {node.MachineId:Tag:MachineId} {node.ScalesetId:Tag:ScalesetId} {node.State:Tag:State}");
|
||||||
@ -730,7 +730,7 @@ public class ScalesetOperations : StatefulOrm<Scaleset, ScalesetState, ScalesetO
|
|||||||
return OneFuzzResultVoid.Ok;
|
return OneFuzzResultVoid.Ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<OneFuzzResult<Scaleset>> GetById(Guid scalesetId) {
|
public async Task<OneFuzzResult<Scaleset>> GetById(ScalesetId scalesetId) {
|
||||||
var data = QueryAsync(filter: Query.RowKey(scalesetId.ToString()));
|
var data = QueryAsync(filter: Query.RowKey(scalesetId.ToString()));
|
||||||
var scaleSets = data is not null ? (await data.ToListAsync()) : null;
|
var scaleSets = data is not null ? (await data.ToListAsync()) : null;
|
||||||
|
|
||||||
@ -800,17 +800,11 @@ public class ScalesetOperations : StatefulOrm<Scaleset, ScalesetState, ScalesetO
|
|||||||
return new List<ScalesetNodeState>();
|
return new List<ScalesetNodeState>();
|
||||||
}
|
}
|
||||||
|
|
||||||
var (nodes, azureNodes) = await (
|
var nodes = _context.NodeOperations.SearchStates(scalesetId: scaleset.ScalesetId);
|
||||||
_context.NodeOperations.SearchStates(scaleset.ScalesetId).ToListAsync().AsTask(),
|
|
||||||
_context.VmssOperations.ListInstanceIds(scaleset.ScalesetId));
|
|
||||||
|
|
||||||
var result = new List<ScalesetNodeState>();
|
var result = new List<ScalesetNodeState>();
|
||||||
foreach (var (machineId, instanceId) in azureNodes) {
|
await foreach (var node in nodes) {
|
||||||
var node = nodes.FirstOrDefault(n => n.MachineId == machineId);
|
result.Add(new ScalesetNodeState(node.MachineId, node.InstanceId, node.State));
|
||||||
result.Add(new ScalesetNodeState(
|
|
||||||
MachineId: machineId,
|
|
||||||
InstanceId: instanceId,
|
|
||||||
node?.State));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
@ -2,25 +2,40 @@
|
|||||||
|
|
||||||
public record ShrinkEntry(Guid ShrinkId);
|
public record ShrinkEntry(Guid ShrinkId);
|
||||||
|
|
||||||
|
public sealed class ShrinkQueue {
|
||||||
public class ShrinkQueue {
|
|
||||||
readonly Guid _baseId;
|
|
||||||
readonly IQueue _queueOps;
|
readonly IQueue _queueOps;
|
||||||
readonly ILogTracer _log;
|
readonly ILogTracer _log;
|
||||||
|
|
||||||
public ShrinkQueue(Guid baseId, IQueue queueOps, ILogTracer log) {
|
public ShrinkQueue(ScalesetId baseId, IQueue queueOps, ILogTracer log)
|
||||||
_baseId = baseId;
|
// backwards compat
|
||||||
|
// scaleset ID used to be a GUID and then this class would format it with "N" format
|
||||||
|
// to retain the same behaviour remove any dashes in the name
|
||||||
|
: this(baseId.ToString().Replace("-", ""), queueOps, log) { }
|
||||||
|
|
||||||
|
public ShrinkQueue(Guid poolId, IQueue queueOps, ILogTracer log)
|
||||||
|
: this(poolId.ToString("N"), queueOps, log) { }
|
||||||
|
|
||||||
|
private ShrinkQueue(string baseId, IQueue queueOps, ILogTracer log) {
|
||||||
|
var name = ShrinkQueueNamePrefix + baseId.ToLowerInvariant();
|
||||||
|
|
||||||
|
// queue names can be no longer than 63 characters
|
||||||
|
// if we exceed that, trim off the end. we will still have
|
||||||
|
// sufficient random chracters to stop collisions from happening
|
||||||
|
if (name.Length > 63) {
|
||||||
|
name = name[..63];
|
||||||
|
}
|
||||||
|
|
||||||
|
QueueName = name;
|
||||||
_queueOps = queueOps;
|
_queueOps = queueOps;
|
||||||
_log = log;
|
_log = log;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string ShrinkQueueNamePrefix => "to-shrink-";
|
public static string ShrinkQueueNamePrefix => "to-shrink-";
|
||||||
|
|
||||||
public override string ToString() {
|
public override string ToString()
|
||||||
return $"{ShrinkQueueNamePrefix}{_baseId:N}";
|
=> QueueName;
|
||||||
}
|
|
||||||
|
|
||||||
public string QueueName => ToString();
|
public string QueueName { get; }
|
||||||
|
|
||||||
public async Async.Task Clear() {
|
public async Async.Task Clear() {
|
||||||
await _queueOps.ClearQueue(QueueName, StorageType.Config);
|
await _queueOps.ClearQueue(QueueName, StorageType.Config);
|
||||||
|
@ -13,23 +13,23 @@ namespace Microsoft.OneFuzz.Service;
|
|||||||
|
|
||||||
public interface IVmssOperations {
|
public interface IVmssOperations {
|
||||||
Async.Task<OneFuzzResultVoid> UpdateScaleInProtection(Scaleset scaleset, string instanceId, bool protectFromScaleIn);
|
Async.Task<OneFuzzResultVoid> UpdateScaleInProtection(Scaleset scaleset, string instanceId, bool protectFromScaleIn);
|
||||||
Async.Task<OneFuzzResult<string>> GetInstanceId(Guid name, Guid vmId);
|
Async.Task<OneFuzzResult<string>> GetInstanceId(ScalesetId name, Guid vmId);
|
||||||
Async.Task<OneFuzzResultVoid> UpdateExtensions(Guid name, IList<VirtualMachineScaleSetExtensionData> extensions);
|
Async.Task<OneFuzzResultVoid> UpdateExtensions(ScalesetId name, IList<VirtualMachineScaleSetExtensionData> extensions);
|
||||||
Async.Task<VirtualMachineScaleSetData?> GetVmss(Guid name);
|
Async.Task<VirtualMachineScaleSetData?> GetVmss(ScalesetId name);
|
||||||
|
|
||||||
Async.Task<IReadOnlyList<string>> ListAvailableSkus(Region region);
|
Async.Task<IReadOnlyList<string>> ListAvailableSkus(Region region);
|
||||||
|
|
||||||
Async.Task<bool> DeleteVmss(Guid name, bool? forceDeletion = null);
|
Async.Task<bool> DeleteVmss(ScalesetId name, bool? forceDeletion = null);
|
||||||
|
|
||||||
Async.Task<IDictionary<Guid, string>> ListInstanceIds(Guid name);
|
Async.Task<IDictionary<Guid, string>> ListInstanceIds(ScalesetId name);
|
||||||
|
|
||||||
Async.Task<long?> GetVmssSize(Guid name);
|
Async.Task<long?> GetVmssSize(ScalesetId name);
|
||||||
|
|
||||||
Async.Task<OneFuzzResultVoid> ResizeVmss(Guid name, long capacity);
|
Async.Task<OneFuzzResultVoid> ResizeVmss(ScalesetId name, long capacity);
|
||||||
|
|
||||||
Async.Task<OneFuzzResultVoid> CreateVmss(
|
Async.Task<OneFuzzResultVoid> CreateVmss(
|
||||||
Region location,
|
Region location,
|
||||||
Guid name,
|
ScalesetId name,
|
||||||
string vmSku,
|
string vmSku,
|
||||||
long vmCount,
|
long vmCount,
|
||||||
ImageReference image,
|
ImageReference image,
|
||||||
@ -41,9 +41,9 @@ public interface IVmssOperations {
|
|||||||
string sshPublicKey,
|
string sshPublicKey,
|
||||||
IDictionary<string, string> tags);
|
IDictionary<string, string> tags);
|
||||||
|
|
||||||
IAsyncEnumerable<VirtualMachineScaleSetVmResource> ListVmss(Guid name);
|
IAsyncEnumerable<VirtualMachineScaleSetVmResource> ListVmss(ScalesetId name);
|
||||||
Async.Task<OneFuzzResultVoid> ReimageNodes(Guid scalesetId, IEnumerable<Node> nodes);
|
Async.Task<OneFuzzResultVoid> ReimageNodes(ScalesetId scalesetId, IEnumerable<Node> nodes);
|
||||||
Async.Task<OneFuzzResultVoid> DeleteNodes(Guid scalesetId, IEnumerable<Node> nodes);
|
Async.Task<OneFuzzResultVoid> DeleteNodes(ScalesetId scalesetId, IEnumerable<Node> nodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class VmssOperations : IVmssOperations {
|
public class VmssOperations : IVmssOperations {
|
||||||
@ -60,7 +60,7 @@ public class VmssOperations : IVmssOperations {
|
|||||||
_cache = cache;
|
_cache = cache;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Async.Task<bool> DeleteVmss(Guid name, bool? forceDeletion = null) {
|
public async Async.Task<bool> DeleteVmss(ScalesetId name, bool? forceDeletion = null) {
|
||||||
var r = GetVmssResource(name);
|
var r = GetVmssResource(name);
|
||||||
var result = await r.DeleteAsync(WaitUntil.Started, forceDeletion: forceDeletion);
|
var result = await r.DeleteAsync(WaitUntil.Started, forceDeletion: forceDeletion);
|
||||||
var raw = result.GetRawResponse();
|
var raw = result.GetRawResponse();
|
||||||
@ -72,7 +72,7 @@ public class VmssOperations : IVmssOperations {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Async.Task<long?> GetVmssSize(Guid name) {
|
public async Async.Task<long?> GetVmssSize(ScalesetId name) {
|
||||||
var vmss = await GetVmss(name);
|
var vmss = await GetVmss(name);
|
||||||
if (vmss == null) {
|
if (vmss == null) {
|
||||||
return null;
|
return null;
|
||||||
@ -80,7 +80,7 @@ public class VmssOperations : IVmssOperations {
|
|||||||
return vmss.Sku.Capacity;
|
return vmss.Sku.Capacity;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Async.Task<OneFuzzResultVoid> ResizeVmss(Guid name, long capacity) {
|
public async Async.Task<OneFuzzResultVoid> ResizeVmss(ScalesetId name, long capacity) {
|
||||||
var canUpdate = await CheckCanUpdate(name);
|
var canUpdate = await CheckCanUpdate(name);
|
||||||
if (canUpdate.IsOk) {
|
if (canUpdate.IsOk) {
|
||||||
var scalesetResource = GetVmssResource(name);
|
var scalesetResource = GetVmssResource(name);
|
||||||
@ -100,7 +100,7 @@ public class VmssOperations : IVmssOperations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private VirtualMachineScaleSetResource GetVmssResource(Guid name) {
|
private VirtualMachineScaleSetResource GetVmssResource(ScalesetId name) {
|
||||||
var id = VirtualMachineScaleSetResource.CreateResourceIdentifier(
|
var id = VirtualMachineScaleSetResource.CreateResourceIdentifier(
|
||||||
_creds.GetSubscription(),
|
_creds.GetSubscription(),
|
||||||
_creds.GetBaseResourceGroup(),
|
_creds.GetBaseResourceGroup(),
|
||||||
@ -108,7 +108,7 @@ public class VmssOperations : IVmssOperations {
|
|||||||
return _creds.ArmClient.GetVirtualMachineScaleSetResource(id);
|
return _creds.ArmClient.GetVirtualMachineScaleSetResource(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
private VirtualMachineScaleSetVmResource GetVmssVmResource(Guid name, string instanceId) {
|
private VirtualMachineScaleSetVmResource GetVmssVmResource(ScalesetId name, string instanceId) {
|
||||||
var id = VirtualMachineScaleSetVmResource.CreateResourceIdentifier(
|
var id = VirtualMachineScaleSetVmResource.CreateResourceIdentifier(
|
||||||
_creds.GetSubscription(),
|
_creds.GetSubscription(),
|
||||||
_creds.GetBaseResourceGroup(),
|
_creds.GetBaseResourceGroup(),
|
||||||
@ -117,7 +117,7 @@ public class VmssOperations : IVmssOperations {
|
|||||||
return _creds.ArmClient.GetVirtualMachineScaleSetVmResource(id);
|
return _creds.ArmClient.GetVirtualMachineScaleSetVmResource(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Async.Task<VirtualMachineScaleSetData?> GetVmss(Guid name) {
|
public async Async.Task<VirtualMachineScaleSetData?> GetVmss(ScalesetId name) {
|
||||||
try {
|
try {
|
||||||
var res = await GetVmssResource(name).GetAsync();
|
var res = await GetVmssResource(name).GetAsync();
|
||||||
_log.Verbose($"getting vmss: {name:Tag:VmssName}");
|
_log.Verbose($"getting vmss: {name:Tag:VmssName}");
|
||||||
@ -127,7 +127,7 @@ public class VmssOperations : IVmssOperations {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Async.Task<OneFuzzResult<VirtualMachineScaleSetData>> CheckCanUpdate(Guid name) {
|
public async Async.Task<OneFuzzResult<VirtualMachineScaleSetData>> CheckCanUpdate(ScalesetId name) {
|
||||||
var vmss = await GetVmss(name);
|
var vmss = await GetVmss(name);
|
||||||
if (vmss is null) {
|
if (vmss is null) {
|
||||||
return OneFuzzResult<VirtualMachineScaleSetData>.Error(ErrorCode.UNABLE_TO_UPDATE, $"vmss not found: {name}");
|
return OneFuzzResult<VirtualMachineScaleSetData>.Error(ErrorCode.UNABLE_TO_UPDATE, $"vmss not found: {name}");
|
||||||
@ -139,7 +139,7 @@ public class VmssOperations : IVmssOperations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public async Async.Task<OneFuzzResultVoid> UpdateExtensions(Guid name, IList<VirtualMachineScaleSetExtensionData> extensions) {
|
public async Async.Task<OneFuzzResultVoid> UpdateExtensions(ScalesetId name, IList<VirtualMachineScaleSetExtensionData> extensions) {
|
||||||
var canUpdate = await CheckCanUpdate(name);
|
var canUpdate = await CheckCanUpdate(name);
|
||||||
if (canUpdate.IsOk) {
|
if (canUpdate.IsOk) {
|
||||||
var res = GetVmssResource(name);
|
var res = GetVmssResource(name);
|
||||||
@ -165,7 +165,7 @@ public class VmssOperations : IVmssOperations {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Async.Task<IDictionary<Guid, string>> ListInstanceIds(Guid name) {
|
public async Async.Task<IDictionary<Guid, string>> ListInstanceIds(ScalesetId name) {
|
||||||
_log.Verbose($"get instance IDs for scaleset {name:Tag:VmssName}");
|
_log.Verbose($"get instance IDs for scaleset {name:Tag:VmssName}");
|
||||||
try {
|
try {
|
||||||
var results = new Dictionary<Guid, string>();
|
var results = new Dictionary<Guid, string>();
|
||||||
@ -185,8 +185,8 @@ public class VmssOperations : IVmssOperations {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed record InstanceIdKey(Guid Scaleset, Guid VmId);
|
private sealed record InstanceIdKey(ScalesetId Scaleset, Guid VmId);
|
||||||
private Task<string> GetInstanceIdForVmId(Guid scaleset, Guid vmId)
|
private Task<string> GetInstanceIdForVmId(ScalesetId scaleset, Guid vmId)
|
||||||
=> _cache.GetOrCreateAsync(new InstanceIdKey(scaleset, vmId), async entry => {
|
=> _cache.GetOrCreateAsync(new InstanceIdKey(scaleset, vmId), async entry => {
|
||||||
var scalesetResource = GetVmssResource(scaleset);
|
var scalesetResource = GetVmssResource(scaleset);
|
||||||
var vmIdString = vmId.ToString();
|
var vmIdString = vmId.ToString();
|
||||||
@ -214,7 +214,7 @@ public class VmssOperations : IVmssOperations {
|
|||||||
}
|
}
|
||||||
})!; // NULLABLE: only this method inserts InstanceIdKey so it cannot be null
|
})!; // NULLABLE: only this method inserts InstanceIdKey so it cannot be null
|
||||||
|
|
||||||
public async Async.Task<OneFuzzResult<VirtualMachineScaleSetVmResource>> GetInstanceVm(Guid name, Guid vmId) {
|
public async Async.Task<OneFuzzResult<VirtualMachineScaleSetVmResource>> GetInstanceVm(ScalesetId name, Guid vmId) {
|
||||||
_log.Info($"get instance ID for scaleset node: {name:Tag:VmssName}:{vmId:Tag:VmId}");
|
_log.Info($"get instance ID for scaleset node: {name:Tag:VmssName}:{vmId:Tag:VmId}");
|
||||||
var instanceId = await GetInstanceId(name, vmId);
|
var instanceId = await GetInstanceId(name, vmId);
|
||||||
if (!instanceId.IsOk) {
|
if (!instanceId.IsOk) {
|
||||||
@ -231,7 +231,7 @@ public class VmssOperations : IVmssOperations {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Async.Task<OneFuzzResult<string>> GetInstanceId(Guid name, Guid vmId) {
|
public async Async.Task<OneFuzzResult<string>> GetInstanceId(ScalesetId name, Guid vmId) {
|
||||||
try {
|
try {
|
||||||
return OneFuzzResult.Ok(await GetInstanceIdForVmId(name, vmId));
|
return OneFuzzResult.Ok(await GetInstanceIdForVmId(name, vmId));
|
||||||
} catch {
|
} catch {
|
||||||
@ -265,7 +265,7 @@ public class VmssOperations : IVmssOperations {
|
|||||||
|
|
||||||
public async Async.Task<OneFuzzResultVoid> CreateVmss(
|
public async Async.Task<OneFuzzResultVoid> CreateVmss(
|
||||||
Region location,
|
Region location,
|
||||||
Guid name,
|
ScalesetId name,
|
||||||
string vmSku,
|
string vmSku,
|
||||||
long vmCount,
|
long vmCount,
|
||||||
ImageReference image,
|
ImageReference image,
|
||||||
@ -397,7 +397,7 @@ public class VmssOperations : IVmssOperations {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public IAsyncEnumerable<VirtualMachineScaleSetVmResource> ListVmss(Guid name)
|
public IAsyncEnumerable<VirtualMachineScaleSetVmResource> ListVmss(ScalesetId name)
|
||||||
=> GetVmssResource(name)
|
=> GetVmssResource(name)
|
||||||
.GetVirtualMachineScaleSetVms()
|
.GetVirtualMachineScaleSetVms()
|
||||||
.SelectAwait(async vm => vm.HasData ? vm : await vm.GetAsync());
|
.SelectAwait(async vm => vm.HasData ? vm : await vm.GetAsync());
|
||||||
@ -431,7 +431,7 @@ public class VmssOperations : IVmssOperations {
|
|||||||
return skuNames;
|
return skuNames;
|
||||||
})!; // NULLABLE: only this method inserts AvailableSkusKey so it cannot be null
|
})!; // NULLABLE: only this method inserts AvailableSkusKey so it cannot be null
|
||||||
|
|
||||||
private async Async.Task<HashSet<string>> ResolveInstanceIds(Guid scalesetId, IEnumerable<Node> nodes) {
|
private async Async.Task<HashSet<string>> ResolveInstanceIds(ScalesetId scalesetId, IEnumerable<Node> nodes) {
|
||||||
|
|
||||||
// only initialize this if we find a missing InstanceId
|
// only initialize this if we find a missing InstanceId
|
||||||
var machineToInstanceLazy = new Lazy<Task<IDictionary<Guid, string>>>(async () => {
|
var machineToInstanceLazy = new Lazy<Task<IDictionary<Guid, string>>>(async () => {
|
||||||
@ -461,7 +461,7 @@ public class VmssOperations : IVmssOperations {
|
|||||||
return instanceIds;
|
return instanceIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Async.Task<OneFuzzResultVoid> ReimageNodes(Guid scalesetId, IEnumerable<Node> nodes) {
|
public async Async.Task<OneFuzzResultVoid> ReimageNodes(ScalesetId scalesetId, IEnumerable<Node> nodes) {
|
||||||
var result = await CheckCanUpdate(scalesetId);
|
var result = await CheckCanUpdate(scalesetId);
|
||||||
if (!result.IsOk) {
|
if (!result.IsOk) {
|
||||||
return OneFuzzResultVoid.Error(result.ErrorV);
|
return OneFuzzResultVoid.Error(result.ErrorV);
|
||||||
@ -514,7 +514,7 @@ public class VmssOperations : IVmssOperations {
|
|||||||
return OneFuzzResultVoid.Ok;
|
return OneFuzzResultVoid.Ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Async.Task<OneFuzzResultVoid> DeleteNodes(Guid scalesetId, IEnumerable<Node> nodes) {
|
public async Async.Task<OneFuzzResultVoid> DeleteNodes(ScalesetId scalesetId, IEnumerable<Node> nodes) {
|
||||||
var result = await CheckCanUpdate(scalesetId);
|
var result = await CheckCanUpdate(scalesetId);
|
||||||
if (!result.IsOk) {
|
if (!result.IsOk) {
|
||||||
_log.Warning($"cannot delete nodes from scaleset {scalesetId} : {result.ErrorV}");
|
_log.Warning($"cannot delete nodes from scaleset {scalesetId} : {result.ErrorV}");
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using Azure.Data.Tables;
|
using Azure.Data.Tables;
|
||||||
|
using Microsoft.OneFuzz.Service;
|
||||||
using Microsoft.OneFuzz.Service.OneFuzzLib.Orm;
|
using Microsoft.OneFuzz.Service.OneFuzzLib.Orm;
|
||||||
|
|
||||||
namespace ApiService.OneFuzzLib.Orm {
|
namespace ApiService.OneFuzzLib.Orm {
|
||||||
@ -15,6 +16,8 @@ namespace ApiService.OneFuzzLib.Orm {
|
|||||||
for (int i = 0; i < args.Length; i++) {
|
for (int i = 0; i < args.Length; i++) {
|
||||||
if (args[i] is Guid g) {
|
if (args[i] is Guid g) {
|
||||||
args[i] = g.ToString();
|
args[i] = g.ToString();
|
||||||
|
} else if (args[i] is IValidatedString s) {
|
||||||
|
args[i] = s.String;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ public class Node : IFromJsonElement<Node> {
|
|||||||
|
|
||||||
public string State => _e.GetStringProperty("state");
|
public string State => _e.GetStringProperty("state");
|
||||||
|
|
||||||
public Guid? ScalesetId => _e.GetNullableGuidProperty("scaleset_id");
|
public string? ScalesetId => _e.GetNullableStringProperty("scaleset_id");
|
||||||
public bool ReimageRequested => _e.GetBoolProperty("reimage_requested");
|
public bool ReimageRequested => _e.GetBoolProperty("reimage_requested");
|
||||||
public bool DeleteRequested => _e.GetBoolProperty("delete_requested");
|
public bool DeleteRequested => _e.GetBoolProperty("delete_requested");
|
||||||
public bool DebugKeepNode => _e.GetBoolProperty("debug_keep_node");
|
public bool DebugKeepNode => _e.GetBoolProperty("debug_keep_node");
|
||||||
@ -45,7 +45,7 @@ public class NodeApi : ApiBase {
|
|||||||
.AddV("machine_id", machineId);
|
.AddV("machine_id", machineId);
|
||||||
return Return<BooleanResult>(await Post(j));
|
return Return<BooleanResult>(await Post(j));
|
||||||
}
|
}
|
||||||
public async Task<Result<IEnumerable<Node>, Error>> Get(Guid? machineId = null, IEnumerable<string>? state = null, Guid? scalesetId = null, string? poolName = null) {
|
public async Task<Result<IEnumerable<Node>, Error>> Get(Guid? machineId = null, IEnumerable<string>? state = null, string? scalesetId = null, string? poolName = null) {
|
||||||
var j = new JsonObject()
|
var j = new JsonObject()
|
||||||
.AddIfNotNullV("machine_id", machineId)
|
.AddIfNotNullV("machine_id", machineId)
|
||||||
.AddIfNotNullEnumerableV("state", state)
|
.AddIfNotNullEnumerableV("state", state)
|
||||||
|
@ -91,7 +91,7 @@ public class ProxyApi : ApiBase {
|
|||||||
base(endpoint, "/api/proxy", request, output) {
|
base(endpoint, "/api/proxy", request, output) {
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Result<IEnumerable<Proxy>, Error>> Get(Guid? scalesetId = null, Guid? machineId = null, int? dstPort = null) {
|
public async Task<Result<IEnumerable<Proxy>, Error>> Get(string? scalesetId = null, Guid? machineId = null, int? dstPort = null) {
|
||||||
var root = new JsonObject()
|
var root = new JsonObject()
|
||||||
.AddIfNotNullV("scaleset_id", scalesetId)
|
.AddIfNotNullV("scaleset_id", scalesetId)
|
||||||
.AddIfNotNullV("machine_id", machineId)
|
.AddIfNotNullV("machine_id", machineId)
|
||||||
@ -101,7 +101,7 @@ public class ProxyApi : ApiBase {
|
|||||||
return IEnumerableResult<Proxy>(r.GetProperty("proxies"));
|
return IEnumerableResult<Proxy>(r.GetProperty("proxies"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<BooleanResult> Delete(Guid scalesetId, Guid machineId, int? dstPort = null) {
|
public async Task<BooleanResult> Delete(string scalesetId, Guid machineId, int? dstPort = null) {
|
||||||
var root = new JsonObject()
|
var root = new JsonObject()
|
||||||
.AddV("scaleset_id", scalesetId)
|
.AddV("scaleset_id", scalesetId)
|
||||||
.AddV("machine_id", machineId)
|
.AddV("machine_id", machineId)
|
||||||
@ -115,10 +115,10 @@ public class ProxyApi : ApiBase {
|
|||||||
return Return<BooleanResult>(r);
|
return Return<BooleanResult>(r);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Result<ProxyGetResult, Error>> Create(Guid scalesetId, Guid machineId, int dstPort, int duration) {
|
public async Task<Result<ProxyGetResult, Error>> Create(string scalesetId, Guid machineId, int dstPort, int duration) {
|
||||||
var root = new JsonObject()
|
var root = new JsonObject()
|
||||||
.AddV("scaleset_id", scalesetId)
|
.AddV("scaleset_id", scalesetId)
|
||||||
.AddV("machin_id", machineId)
|
.AddV("machine_id", machineId)
|
||||||
.AddV("dst_port", dstPort)
|
.AddV("dst_port", dstPort)
|
||||||
.AddV("duration", duration);
|
.AddV("duration", duration);
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ public class Scaleset : IFromJsonElement<Scaleset> {
|
|||||||
public Scaleset(JsonElement e) => _e = e;
|
public Scaleset(JsonElement e) => _e = e;
|
||||||
public static Scaleset Convert(JsonElement e) => new(e);
|
public static Scaleset Convert(JsonElement e) => new(e);
|
||||||
|
|
||||||
public Guid ScalesetId => _e.GetGuidProperty("scaleset_id");
|
public string ScalesetId => _e.GetStringProperty("scaleset_id");
|
||||||
public string PoolName => _e.GetStringProperty("pool_name");
|
public string PoolName => _e.GetStringProperty("pool_name");
|
||||||
public string State => _e.GetStringProperty("state");
|
public string State => _e.GetStringProperty("state");
|
||||||
|
|
||||||
@ -59,7 +59,7 @@ public class ScalesetApi : ApiBase {
|
|||||||
base(endpoint, "/api/Scaleset", request, output) { }
|
base(endpoint, "/api/Scaleset", request, output) { }
|
||||||
|
|
||||||
|
|
||||||
public async Task<Result<IEnumerable<Scaleset>, Error>> Get(Guid? id = null, string? state = null, bool? includeAuth = false) {
|
public async Task<Result<IEnumerable<Scaleset>, Error>> Get(string? id = null, string? state = null, bool? includeAuth = false) {
|
||||||
var j = new JsonObject()
|
var j = new JsonObject()
|
||||||
.AddIfNotNullV("scaleset_id", id)
|
.AddIfNotNullV("scaleset_id", id)
|
||||||
.AddIfNotNullV("state", state)
|
.AddIfNotNullV("state", state)
|
||||||
@ -84,14 +84,14 @@ public class ScalesetApi : ApiBase {
|
|||||||
return Result<Scaleset>(await Post(rootScalesetCreate));
|
return Result<Scaleset>(await Post(rootScalesetCreate));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Result<Scaleset, Error>> Patch(Guid id, int size) {
|
public async Task<Result<Scaleset, Error>> Patch(string id, int size) {
|
||||||
var scalesetPatch = new JsonObject()
|
var scalesetPatch = new JsonObject()
|
||||||
.AddV("scaleset_id", id)
|
.AddV("scaleset_id", id)
|
||||||
.AddV("size", size);
|
.AddV("size", size);
|
||||||
return Result<Scaleset>(await Patch(scalesetPatch));
|
return Result<Scaleset>(await Patch(scalesetPatch));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<BooleanResult> Delete(Guid id, bool now) {
|
public async Task<BooleanResult> Delete(string id, bool now) {
|
||||||
var scalesetDelete = new JsonObject()
|
var scalesetDelete = new JsonObject()
|
||||||
.AddV("scaleset_id", id)
|
.AddV("scaleset_id", id)
|
||||||
.AddV("now", now);
|
.AddV("now", now);
|
||||||
@ -99,7 +99,7 @@ public class ScalesetApi : ApiBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public async Task<Scaleset> WaitWhile(Guid id, Func<Scaleset, bool> wait) {
|
public async Task<Scaleset> WaitWhile(string id, Func<Scaleset, bool> wait) {
|
||||||
var currentState = "";
|
var currentState = "";
|
||||||
Scaleset newScaleset;
|
Scaleset newScaleset;
|
||||||
do {
|
do {
|
||||||
|
@ -30,7 +30,7 @@ public abstract class AgentRegistrationTestsBase : FunctionTestBase {
|
|||||||
|
|
||||||
private readonly Guid _machineId = Guid.NewGuid();
|
private readonly Guid _machineId = Guid.NewGuid();
|
||||||
private readonly Guid _poolId = Guid.NewGuid();
|
private readonly Guid _poolId = Guid.NewGuid();
|
||||||
private readonly Guid _scalesetId = Guid.NewGuid();
|
private readonly ScalesetId _scalesetId = ScalesetId.Parse($"scaleset-{Guid.NewGuid()}");
|
||||||
private readonly PoolName _poolName = PoolName.Parse($"pool-{Guid.NewGuid()}");
|
private readonly PoolName _poolName = PoolName.Parse($"pool-{Guid.NewGuid()}");
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
@ -58,6 +58,7 @@ public sealed class TestContext : IOnefuzzContext {
|
|||||||
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),
|
Repro r => ReproOperations.Insert(r),
|
||||||
|
Scaleset ss => ScalesetOperations.Insert(ss),
|
||||||
NodeTasks nt => NodeTasksOperations.Insert(nt),
|
NodeTasks nt => NodeTasksOperations.Insert(nt),
|
||||||
InstanceConfig ic => ConfigOperations.Insert(ic),
|
InstanceConfig ic => ConfigOperations.Insert(ic),
|
||||||
Notification n => NotificationOperations.Insert(n),
|
Notification n => NotificationOperations.Insert(n),
|
||||||
|
@ -20,40 +20,40 @@ sealed class TestVmssOperations : IVmssOperations {
|
|||||||
|
|
||||||
/* below not implemented */
|
/* below not implemented */
|
||||||
|
|
||||||
public Task<OneFuzzResultVoid> CreateVmss(Region location, Guid name, string vmSku, long vmCount, ImageReference image, string networkId, bool? spotInstance, bool ephemeralOsDisks, IList<VirtualMachineScaleSetExtensionData>? extensions, string password, string sshPublicKey, IDictionary<string, string> tags) {
|
public Task<OneFuzzResultVoid> CreateVmss(Region location, ScalesetId name, string vmSku, long vmCount, ImageReference image, string networkId, bool? spotInstance, bool ephemeralOsDisks, IList<VirtualMachineScaleSetExtensionData>? extensions, string password, string sshPublicKey, IDictionary<string, string> tags) {
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<bool> DeleteVmss(Guid name, bool? forceDeletion = null) {
|
public Task<bool> DeleteVmss(ScalesetId name, bool? forceDeletion = null) {
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<OneFuzzResult<string>> GetInstanceId(Guid name, Guid vmId) {
|
public Task<OneFuzzResult<string>> GetInstanceId(ScalesetId name, Guid vmId) {
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<VirtualMachineScaleSetData?> GetVmss(Guid name) {
|
public Task<VirtualMachineScaleSetData?> GetVmss(ScalesetId name) {
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<long?> GetVmssSize(Guid name) {
|
public Task<long?> GetVmssSize(ScalesetId name) {
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public Task<IDictionary<Guid, string>> ListInstanceIds(Guid name) {
|
public Task<IDictionary<Guid, string>> ListInstanceIds(ScalesetId name) {
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public IAsyncEnumerable<VirtualMachineScaleSetVmResource> ListVmss(Guid name) {
|
public IAsyncEnumerable<VirtualMachineScaleSetVmResource> ListVmss(ScalesetId name) {
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<OneFuzzResultVoid> ResizeVmss(Guid name, long capacity) {
|
public Task<OneFuzzResultVoid> ResizeVmss(ScalesetId name, long capacity) {
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<OneFuzzResultVoid> UpdateExtensions(Guid name, IList<VirtualMachineScaleSetExtensionData> extensions) {
|
public Task<OneFuzzResultVoid> UpdateExtensions(ScalesetId name, IList<VirtualMachineScaleSetExtensionData> extensions) {
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,11 +61,11 @@ sealed class TestVmssOperations : IVmssOperations {
|
|||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<OneFuzzResultVoid> ReimageNodes(Guid scalesetId, IEnumerable<Node> nodes) {
|
public Task<OneFuzzResultVoid> ReimageNodes(ScalesetId scalesetId, IEnumerable<Node> nodes) {
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Async.Task<OneFuzzResultVoid> DeleteNodes(Guid scalesetId, IEnumerable<Node> nodes) {
|
public Async.Task<OneFuzzResultVoid> DeleteNodes(ScalesetId scalesetId, IEnumerable<Node> nodes) {
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,10 +24,12 @@ public class AzuriteNodeTest : NodeTestBase {
|
|||||||
|
|
||||||
public abstract class NodeTestBase : FunctionTestBase {
|
public abstract class NodeTestBase : FunctionTestBase {
|
||||||
public NodeTestBase(ITestOutputHelper output, IStorage storage)
|
public NodeTestBase(ITestOutputHelper output, IStorage storage)
|
||||||
: base(output, storage) { }
|
: base(output, storage) {
|
||||||
|
_scalesetId = Scaleset.GenerateNewScalesetId(_poolName);
|
||||||
|
}
|
||||||
|
|
||||||
private readonly Guid _machineId = Guid.NewGuid();
|
private readonly Guid _machineId = Guid.NewGuid();
|
||||||
private readonly Guid _scalesetId = Guid.NewGuid();
|
private readonly ScalesetId _scalesetId;
|
||||||
private readonly PoolName _poolName = PoolName.Parse($"pool-{Guid.NewGuid()}");
|
private readonly PoolName _poolName = PoolName.Parse($"pool-{Guid.NewGuid()}");
|
||||||
private readonly string _version = Guid.NewGuid().ToString();
|
private readonly string _version = Guid.NewGuid().ToString();
|
||||||
|
|
||||||
|
@ -45,7 +45,7 @@ public abstract class ScalesetTestBase : FunctionTestBase {
|
|||||||
public async Async.Task Search_SpecificScaleset_ReturnsErrorIfNoneFound() {
|
public async Async.Task Search_SpecificScaleset_ReturnsErrorIfNoneFound() {
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
||||||
|
|
||||||
var req = new ScalesetSearch(ScalesetId: Guid.NewGuid());
|
var req = new ScalesetSearch(ScalesetId: ScalesetId.Parse(Guid.NewGuid().ToString()));
|
||||||
var func = new ScalesetFunction(Logger, auth, Context);
|
var func = new ScalesetFunction(Logger, auth, Context);
|
||||||
var result = await func.Run(TestHttpRequestData.FromJson("GET", req));
|
var result = await func.Run(TestHttpRequestData.FromJson("GET", req));
|
||||||
|
|
||||||
@ -66,6 +66,33 @@ public abstract class ScalesetTestBase : FunctionTestBase {
|
|||||||
Assert.Equal("[]", BodyAsString(result));
|
Assert.Equal("[]", BodyAsString(result));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Async.Task Search_CanFindScaleset_AndReturnsNodes() {
|
||||||
|
var scalesetId = ScalesetId.Parse(Guid.NewGuid().ToString());
|
||||||
|
var poolName = PoolName.Parse($"pool-${Guid.NewGuid()}");
|
||||||
|
var poolId = Guid.NewGuid();
|
||||||
|
|
||||||
|
await Context.InsertAll(
|
||||||
|
new Pool(poolName, poolId, Os.Linux, Managed: true, Architecture.x86_64, PoolState.Running),
|
||||||
|
// scaleset to be found must exist
|
||||||
|
new Scaleset(poolName, scalesetId, ScalesetState.Running, "", ImageReference.MustParse("x:y:z:v"), Region.Parse("region"), 1, null, false, false, new Dictionary<string, string>()),
|
||||||
|
// some nodes
|
||||||
|
new Node(poolName, Guid.NewGuid(), poolId, "version", ScalesetId: scalesetId),
|
||||||
|
new Node(poolName, Guid.NewGuid(), poolId, "version", ScalesetId: scalesetId)
|
||||||
|
);
|
||||||
|
|
||||||
|
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
||||||
|
|
||||||
|
var req = new ScalesetSearch(ScalesetId: scalesetId);
|
||||||
|
var func = new ScalesetFunction(Logger, auth, Context);
|
||||||
|
var result = await func.Run(TestHttpRequestData.FromJson("GET", req));
|
||||||
|
|
||||||
|
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
|
||||||
|
var resp = BodyAs<ScalesetResponse>(result);
|
||||||
|
Assert.Equal(scalesetId, resp.ScalesetId);
|
||||||
|
Assert.Equal(2, resp.Nodes?.Count);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Async.Task Create_Scaleset() {
|
public async Async.Task Create_Scaleset() {
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
||||||
|
@ -91,26 +91,32 @@ namespace Tests {
|
|||||||
where PoolName.IsValid(name.Get)
|
where PoolName.IsValid(name.Get)
|
||||||
select PoolName.Parse(name.Get);
|
select PoolName.Parse(name.Get);
|
||||||
|
|
||||||
|
public static Gen<ScalesetId> ScalesetIdGen { get; }
|
||||||
|
= from name in Arb.Generate<NonEmptyString>()
|
||||||
|
where ScalesetId.IsValid(name.Get)
|
||||||
|
select ScalesetId.Parse(name.Get);
|
||||||
|
|
||||||
public static Gen<Region> RegionGen { get; }
|
public static Gen<Region> RegionGen { get; }
|
||||||
= from name in Arb.Generate<NonEmptyString>()
|
= from name in Arb.Generate<NonEmptyString>()
|
||||||
where Region.IsValid(name.Get)
|
where Region.IsValid(name.Get)
|
||||||
select Region.Parse(name.Get);
|
select Region.Parse(name.Get);
|
||||||
|
|
||||||
public static Gen<Node> Node { get; }
|
public static Gen<Node> Node { get; }
|
||||||
= from arg in Arb.Generate<Tuple<Tuple<DateTimeOffset?, Guid?, Guid, NodeState>, Tuple<Guid?, DateTimeOffset, string, bool, bool, bool>>>()
|
= from arg in Arb.Generate<Tuple<Tuple<DateTimeOffset?, Guid?, Guid, NodeState>, Tuple<DateTimeOffset, string, bool, bool, bool>>>()
|
||||||
from poolName in PoolNameGen
|
from poolName in PoolNameGen
|
||||||
|
from scalesetId in Arb.Generate<Guid>()
|
||||||
select new Node(
|
select new Node(
|
||||||
InitializedAt: arg.Item1.Item1,
|
InitializedAt: arg.Item1.Item1,
|
||||||
PoolName: poolName,
|
PoolName: poolName,
|
||||||
PoolId: arg.Item1.Item3,
|
PoolId: arg.Item1.Item3,
|
||||||
MachineId: arg.Item1.Item3,
|
MachineId: arg.Item1.Item3,
|
||||||
State: arg.Item1.Item4,
|
State: arg.Item1.Item4,
|
||||||
ScalesetId: arg.Item2.Item1,
|
ScalesetId: ScalesetId.Parse(scalesetId.ToString()),
|
||||||
Heartbeat: arg.Item2.Item2,
|
Heartbeat: arg.Item2.Item1,
|
||||||
Version: arg.Item2.Item3,
|
Version: arg.Item2.Item2,
|
||||||
ReimageRequested: arg.Item2.Item4,
|
ReimageRequested: arg.Item2.Item3,
|
||||||
DeleteRequested: arg.Item2.Item5,
|
DeleteRequested: arg.Item2.Item4,
|
||||||
DebugKeepNode: arg.Item2.Item6);
|
DebugKeepNode: arg.Item2.Item5);
|
||||||
|
|
||||||
public static Gen<ProxyForward> ProxyForward { get; } =
|
public static Gen<ProxyForward> ProxyForward { get; } =
|
||||||
from region in RegionGen
|
from region in RegionGen
|
||||||
@ -124,7 +130,7 @@ namespace Tests {
|
|||||||
select new ProxyForward(
|
select new ProxyForward(
|
||||||
Region: region,
|
Region: region,
|
||||||
Port: port,
|
Port: port,
|
||||||
ScalesetId: scalesetId,
|
ScalesetId: ScalesetId.Parse(scalesetId.ToString()),
|
||||||
MachineId: machineId,
|
MachineId: machineId,
|
||||||
ProxyId: proxyId,
|
ProxyId: proxyId,
|
||||||
DstPort: dstPort,
|
DstPort: dstPort,
|
||||||
@ -241,18 +247,19 @@ namespace Tests {
|
|||||||
|
|
||||||
public static Gen<Scaleset> Scaleset { get; }
|
public static Gen<Scaleset> Scaleset { get; }
|
||||||
= from arg in Arb.Generate<Tuple<
|
= from arg in Arb.Generate<Tuple<
|
||||||
Tuple<Guid, ScalesetState, Authentication?, string>,
|
Tuple<ScalesetState, Authentication?, string>,
|
||||||
Tuple<int, bool, bool, bool, Error?, Guid?>,
|
Tuple<int, bool, bool, bool, Error?, Guid?>,
|
||||||
Tuple<Guid?, Dictionary<string, string>>>>()
|
Tuple<Guid?, Dictionary<string, string>>>>()
|
||||||
|
from scalesetId in Arb.Generate<Guid>()
|
||||||
from poolName in PoolNameGen
|
from poolName in PoolNameGen
|
||||||
from region in RegionGen
|
from region in RegionGen
|
||||||
from image in ImageReferenceGen
|
from image in ImageReferenceGen
|
||||||
select new Scaleset(
|
select new Scaleset(
|
||||||
PoolName: poolName,
|
PoolName: poolName,
|
||||||
ScalesetId: arg.Item1.Item1,
|
ScalesetId: ScalesetId.Parse(scalesetId.ToString()),
|
||||||
State: arg.Item1.Item2,
|
State: arg.Item1.Item1,
|
||||||
Auth: arg.Item1.Item3,
|
Auth: arg.Item1.Item2,
|
||||||
VmSku: arg.Item1.Item4,
|
VmSku: arg.Item1.Item3,
|
||||||
Image: image,
|
Image: image,
|
||||||
Region: region,
|
Region: region,
|
||||||
|
|
||||||
@ -511,6 +518,7 @@ namespace Tests {
|
|||||||
public class OrmArb {
|
public class OrmArb {
|
||||||
|
|
||||||
public static Arbitrary<PoolName> PoolName { get; } = OrmGenerators.PoolNameGen.ToArbitrary();
|
public static Arbitrary<PoolName> PoolName { get; } = OrmGenerators.PoolNameGen.ToArbitrary();
|
||||||
|
public static Arbitrary<ScalesetId> ScalesetId { get; } = OrmGenerators.ScalesetIdGen.ToArbitrary();
|
||||||
|
|
||||||
public static Arbitrary<IReadOnlyList<T>> ReadOnlyList<T>()
|
public static Arbitrary<IReadOnlyList<T>> ReadOnlyList<T>()
|
||||||
=> Arb.Default.List<T>().Convert(x => (IReadOnlyList<T>)x, x => (List<T>)x);
|
=> Arb.Default.List<T>().Convert(x => (IReadOnlyList<T>)x, x => (List<T>)x);
|
||||||
|
@ -236,7 +236,9 @@ namespace Tests {
|
|||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void TestEventSerialization() {
|
public void TestEventSerialization() {
|
||||||
var expectedEvent = new EventMessage(Guid.NewGuid(), EventType.NodeHeartbeat, new EventNodeHeartbeat(Guid.NewGuid(), Guid.NewGuid(), PoolName.Parse("test-Poool"), NodeState.Busy), Guid.NewGuid(), "test", DateTime.UtcNow);
|
var scalesetId = ScalesetId.Parse(Guid.NewGuid().ToString());
|
||||||
|
var hb = new EventNodeHeartbeat(Guid.NewGuid(), scalesetId, PoolName.Parse("test-Poool"), NodeState.Busy);
|
||||||
|
var expectedEvent = new EventMessage(Guid.NewGuid(), EventType.NodeHeartbeat, hb, Guid.NewGuid(), "test", DateTime.UtcNow);
|
||||||
var serialized = JsonSerializer.Serialize(expectedEvent, EntityConverter.GetJsonSerializerOptions());
|
var serialized = JsonSerializer.Serialize(expectedEvent, EntityConverter.GetJsonSerializerOptions());
|
||||||
var actualEvent = JsonSerializer.Deserialize<EventMessage>((string)serialized, EntityConverter.GetJsonSerializerOptions());
|
var actualEvent = JsonSerializer.Deserialize<EventMessage>((string)serialized, EntityConverter.GetJsonSerializerOptions());
|
||||||
Assert.Equal(expectedEvent, actualEvent);
|
Assert.Equal(expectedEvent, actualEvent);
|
||||||
|
@ -17,7 +17,7 @@ namespace Tests {
|
|||||||
var query2 = NodeOperations.SearchStatesQuery(poolId: Guid.Parse("3b0426d3-9bde-4ae8-89ac-4edf0d3b3618"));
|
var query2 = NodeOperations.SearchStatesQuery(poolId: Guid.Parse("3b0426d3-9bde-4ae8-89ac-4edf0d3b3618"));
|
||||||
Assert.Equal("((pool_id eq '3b0426d3-9bde-4ae8-89ac-4edf0d3b3618'))", query2);
|
Assert.Equal("((pool_id eq '3b0426d3-9bde-4ae8-89ac-4edf0d3b3618'))", query2);
|
||||||
|
|
||||||
var query3 = NodeOperations.SearchStatesQuery(scaleSetId: Guid.Parse("4c96dd6b-9bdb-4758-9720-1010c244fa4b"));
|
var query3 = NodeOperations.SearchStatesQuery(scaleSetId: ScalesetId.Parse("4c96dd6b-9bdb-4758-9720-1010c244fa4b"));
|
||||||
Assert.Equal("((scaleset_id eq '4c96dd6b-9bdb-4758-9720-1010c244fa4b'))", query3);
|
Assert.Equal("((scaleset_id eq '4c96dd6b-9bdb-4758-9720-1010c244fa4b'))", query3);
|
||||||
|
|
||||||
var query4 = NodeOperations.SearchStatesQuery(states: new[] { NodeState.Free, NodeState.Done, NodeState.Ready });
|
var query4 = NodeOperations.SearchStatesQuery(states: new[] { NodeState.Free, NodeState.Done, NodeState.Ready });
|
||||||
@ -25,7 +25,7 @@ namespace Tests {
|
|||||||
|
|
||||||
var query7 = NodeOperations.SearchStatesQuery(
|
var query7 = NodeOperations.SearchStatesQuery(
|
||||||
poolId: Guid.Parse("3b0426d3-9bde-4ae8-89ac-4edf0d3b3618"),
|
poolId: Guid.Parse("3b0426d3-9bde-4ae8-89ac-4edf0d3b3618"),
|
||||||
scaleSetId: Guid.Parse("4c96dd6b-9bdb-4758-9720-1010c244fa4b"),
|
scaleSetId: ScalesetId.Parse("4c96dd6b-9bdb-4758-9720-1010c244fa4b"),
|
||||||
states: new[] { NodeState.Free, NodeState.Done, NodeState.Ready });
|
states: new[] { NodeState.Free, NodeState.Done, NodeState.Ready });
|
||||||
Assert.Equal("((pool_id eq '3b0426d3-9bde-4ae8-89ac-4edf0d3b3618')) and ((scaleset_id eq '4c96dd6b-9bdb-4758-9720-1010c244fa4b')) and (((state eq 'free') or (state eq 'done') or (state eq 'ready')))", query7);
|
Assert.Equal("((pool_id eq '3b0426d3-9bde-4ae8-89ac-4edf0d3b3618')) and ((scaleset_id eq '4c96dd6b-9bdb-4758-9720-1010c244fa4b')) and (((state eq 'free') or (state eq 'done') or (state eq 'ready')))", query7);
|
||||||
}
|
}
|
||||||
@ -33,7 +33,7 @@ namespace Tests {
|
|||||||
[Fact]
|
[Fact]
|
||||||
public void QueryFilterTest() {
|
public void QueryFilterTest() {
|
||||||
|
|
||||||
var scalesetId = Guid.Parse("3b0426d3-9bde-4ae8-89ac-4edf0d3b3618");
|
var scalesetId = ScalesetId.Parse(Guid.Parse("3b0426d3-9bde-4ae8-89ac-4edf0d3b3618").ToString());
|
||||||
var proxyId = Guid.Parse("4c96dd6b-9bdb-4758-9720-1010c244fa4b");
|
var proxyId = Guid.Parse("4c96dd6b-9bdb-4758-9720-1010c244fa4b");
|
||||||
var region = "westus2";
|
var region = "westus2";
|
||||||
var outdated = false;
|
var outdated = false;
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using System.Text.Json;
|
using System;
|
||||||
|
using System.Text.Json;
|
||||||
using Microsoft.OneFuzz.Service;
|
using Microsoft.OneFuzz.Service;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
@ -42,4 +43,40 @@ public class ValidatedStringTests {
|
|||||||
public void PoolNames(string name, bool valid) {
|
public void PoolNames(string name, bool valid) {
|
||||||
Assert.Equal(valid, PoolName.IsValid(name));
|
Assert.Equal(valid, PoolName.IsValid(name));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("", false)]
|
||||||
|
[InlineData("abc", true)]
|
||||||
|
[InlineData("a-bc", true)]
|
||||||
|
[InlineData("-abc", false)]
|
||||||
|
[InlineData("abc-", false)]
|
||||||
|
[InlineData("ef052a0d-f235-4115-bd47-b359bcc5078b", true)]
|
||||||
|
public void ScalesetIds(string name, bool valid) {
|
||||||
|
Assert.Equal(valid, ScalesetId.IsValid(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readonly Guid _fixedGuid = Guid.Parse("3b24ba21-1cad-4b07-8655-914754485838");
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ScalesetId_FromBasicPool() {
|
||||||
|
var pool = PoolName.Parse("pool");
|
||||||
|
var id = Scaleset.GenerateNewScalesetIdUsingGuid(pool, _fixedGuid).ToString();
|
||||||
|
Assert.Equal("pool-3b24ba211cad4b078655914754485838", id);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ScalesetId_FromReallyLongPool() {
|
||||||
|
var pool = PoolName.Parse(new string('x', 100));
|
||||||
|
var id = Scaleset.GenerateNewScalesetIdUsingGuid(pool, _fixedGuid).ToString();
|
||||||
|
Assert.Equal(64, id.Length);
|
||||||
|
Assert.Equal($"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-3b24ba211cad4b078655914754485838", id);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ScalesetId_FromPoolWithBadCharacters() {
|
||||||
|
var pool = PoolName.Parse("_.-po-!?(*!&@#$)o_.l-._");
|
||||||
|
var id = Scaleset.GenerateNewScalesetIdUsingGuid(pool, _fixedGuid).ToString();
|
||||||
|
// hyphens preserved except at start and end, and underscores turned into hyphens
|
||||||
|
Assert.Equal($"po-o-l-3b24ba211cad4b078655914754485838", id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1451,11 +1451,10 @@ class Node(Endpoint):
|
|||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
state: Optional[List[enums.NodeState]] = None,
|
state: Optional[List[enums.NodeState]] = None,
|
||||||
scaleset_id: Optional[UUID_EXPANSION] = None,
|
scaleset_id: Optional[str] = None,
|
||||||
pool_name: Optional[primitives.PoolName] = None,
|
pool_name: Optional[primitives.PoolName] = None,
|
||||||
) -> List[models.Node]:
|
) -> List[models.Node]:
|
||||||
self.logger.debug("list nodes")
|
self.logger.debug("list nodes")
|
||||||
scaleset_id_expanded: Optional[UUID] = None
|
|
||||||
|
|
||||||
if pool_name is not None:
|
if pool_name is not None:
|
||||||
pool_name = primitives.PoolName(
|
pool_name = primitives.PoolName(
|
||||||
@ -1467,18 +1466,11 @@ class Node(Endpoint):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if scaleset_id is not None:
|
|
||||||
scaleset_id_expanded = self._disambiguate_uuid(
|
|
||||||
"scaleset_id",
|
|
||||||
scaleset_id,
|
|
||||||
lambda: [str(x.scaleset_id) for x in self.onefuzz.scalesets.list()],
|
|
||||||
)
|
|
||||||
|
|
||||||
return self._req_model_list(
|
return self._req_model_list(
|
||||||
"GET",
|
"GET",
|
||||||
models.Node,
|
models.Node,
|
||||||
data=requests.NodeSearch(
|
data=requests.NodeSearch(
|
||||||
scaleset_id=scaleset_id_expanded, state=state, pool_name=pool_name
|
scaleset_id=scaleset_id, state=state, pool_name=pool_name
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1510,7 +1502,7 @@ class Scaleset(Endpoint):
|
|||||||
|
|
||||||
def _expand_scaleset_machine(
|
def _expand_scaleset_machine(
|
||||||
self,
|
self,
|
||||||
scaleset_id: UUID_EXPANSION,
|
scaleset_id: str,
|
||||||
machine_id: UUID_EXPANSION,
|
machine_id: UUID_EXPANSION,
|
||||||
*,
|
*,
|
||||||
include_auth: bool = False,
|
include_auth: bool = False,
|
||||||
@ -1577,54 +1569,32 @@ class Scaleset(Endpoint):
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
def shutdown(
|
def shutdown(self, scaleset_id: str, *, now: bool = False) -> responses.BoolResult:
|
||||||
self, scaleset_id: UUID_EXPANSION, *, now: bool = False
|
self.logger.debug("shutdown scaleset: %s (now: %s)", scaleset_id, now)
|
||||||
) -> responses.BoolResult:
|
|
||||||
scaleset_id_expanded = self._disambiguate_uuid(
|
|
||||||
"scaleset_id",
|
|
||||||
scaleset_id,
|
|
||||||
lambda: [str(x.scaleset_id) for x in self.list()],
|
|
||||||
)
|
|
||||||
|
|
||||||
self.logger.debug("shutdown scaleset: %s (now: %s)", scaleset_id_expanded, now)
|
|
||||||
return self._req_model(
|
return self._req_model(
|
||||||
"DELETE",
|
"DELETE",
|
||||||
responses.BoolResult,
|
responses.BoolResult,
|
||||||
data=requests.ScalesetStop(scaleset_id=scaleset_id_expanded, now=now),
|
data=requests.ScalesetStop(scaleset_id=scaleset_id, now=now),
|
||||||
)
|
)
|
||||||
|
|
||||||
def get(
|
def get(self, scaleset_id: str, *, include_auth: bool = False) -> models.Scaleset:
|
||||||
self, scaleset_id: UUID_EXPANSION, *, include_auth: bool = False
|
|
||||||
) -> models.Scaleset:
|
|
||||||
self.logger.debug("get scaleset: %s", scaleset_id)
|
self.logger.debug("get scaleset: %s", scaleset_id)
|
||||||
scaleset_id_expanded = self._disambiguate_uuid(
|
|
||||||
"scaleset_id",
|
|
||||||
scaleset_id,
|
|
||||||
lambda: [str(x.scaleset_id) for x in self.list()],
|
|
||||||
)
|
|
||||||
|
|
||||||
return self._req_model(
|
return self._req_model(
|
||||||
"GET",
|
"GET",
|
||||||
models.Scaleset,
|
models.Scaleset,
|
||||||
data=requests.ScalesetSearch(
|
data=requests.ScalesetSearch(
|
||||||
scaleset_id=scaleset_id_expanded, include_auth=include_auth
|
scaleset_id=scaleset_id, include_auth=include_auth
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
def update(
|
def update(
|
||||||
self, scaleset_id: UUID_EXPANSION, *, size: Optional[int] = None
|
self, scaleset_id: str, *, size: Optional[int] = None
|
||||||
) -> models.Scaleset:
|
) -> models.Scaleset:
|
||||||
self.logger.debug("update scaleset: %s", scaleset_id)
|
self.logger.debug("update scaleset: %s", scaleset_id)
|
||||||
scaleset_id_expanded = self._disambiguate_uuid(
|
|
||||||
"scaleset_id",
|
|
||||||
scaleset_id,
|
|
||||||
lambda: [str(x.scaleset_id) for x in self.list()],
|
|
||||||
)
|
|
||||||
|
|
||||||
return self._req_model(
|
return self._req_model(
|
||||||
"PATCH",
|
"PATCH",
|
||||||
models.Scaleset,
|
models.Scaleset,
|
||||||
data=requests.ScalesetUpdate(scaleset_id=scaleset_id_expanded, size=size),
|
data=requests.ScalesetUpdate(scaleset_id=scaleset_id, size=size),
|
||||||
)
|
)
|
||||||
|
|
||||||
def list(
|
def list(
|
||||||
@ -1645,7 +1615,7 @@ class ScalesetProxy(Endpoint):
|
|||||||
|
|
||||||
def delete(
|
def delete(
|
||||||
self,
|
self,
|
||||||
scaleset_id: UUID_EXPANSION,
|
scaleset_id: str,
|
||||||
machine_id: UUID_EXPANSION,
|
machine_id: UUID_EXPANSION,
|
||||||
*,
|
*,
|
||||||
dst_port: Optional[int] = None,
|
dst_port: Optional[int] = None,
|
||||||
@ -1681,7 +1651,7 @@ class ScalesetProxy(Endpoint):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def get(
|
def get(
|
||||||
self, scaleset_id: UUID_EXPANSION, machine_id: UUID_EXPANSION, dst_port: int
|
self, scaleset_id: str, machine_id: UUID_EXPANSION, dst_port: int
|
||||||
) -> responses.ProxyGetResult:
|
) -> responses.ProxyGetResult:
|
||||||
"""Get information about a specific job"""
|
"""Get information about a specific job"""
|
||||||
(
|
(
|
||||||
@ -1705,7 +1675,7 @@ class ScalesetProxy(Endpoint):
|
|||||||
|
|
||||||
def create(
|
def create(
|
||||||
self,
|
self,
|
||||||
scaleset_id: UUID_EXPANSION,
|
scaleset_id: str,
|
||||||
machine_id: UUID_EXPANSION,
|
machine_id: UUID_EXPANSION,
|
||||||
dst_port: int,
|
dst_port: int,
|
||||||
*,
|
*,
|
||||||
|
@ -103,7 +103,7 @@ class DebugScaleset(Command):
|
|||||||
"""Debug tasks"""
|
"""Debug tasks"""
|
||||||
|
|
||||||
def _get_proxy_setup(
|
def _get_proxy_setup(
|
||||||
self, scaleset_id: UUID, machine_id: UUID, port: int, duration: Optional[int]
|
self, scaleset_id: str, machine_id: UUID, port: int, duration: Optional[int]
|
||||||
) -> Tuple[bool, str, Optional[Tuple[str, int]]]:
|
) -> Tuple[bool, str, Optional[Tuple[str, int]]]:
|
||||||
proxy = self.onefuzz.scaleset_proxy.create(
|
proxy = self.onefuzz.scaleset_proxy.create(
|
||||||
scaleset_id, machine_id, port, duration=duration
|
scaleset_id, machine_id, port, duration=duration
|
||||||
@ -115,7 +115,7 @@ class DebugScaleset(Command):
|
|||||||
|
|
||||||
def rdp(
|
def rdp(
|
||||||
self,
|
self,
|
||||||
scaleset_id: UUID_EXPANSION,
|
scaleset_id: str,
|
||||||
machine_id: UUID_EXPANSION,
|
machine_id: UUID_EXPANSION,
|
||||||
duration: Optional[int] = 1,
|
duration: Optional[int] = 1,
|
||||||
) -> None:
|
) -> None:
|
||||||
@ -144,7 +144,7 @@ class DebugScaleset(Command):
|
|||||||
|
|
||||||
def ssh(
|
def ssh(
|
||||||
self,
|
self,
|
||||||
scaleset_id: UUID_EXPANSION,
|
scaleset_id: str,
|
||||||
machine_id: UUID_EXPANSION,
|
machine_id: UUID_EXPANSION,
|
||||||
duration: Optional[int] = 1,
|
duration: Optional[int] = 1,
|
||||||
command: Optional[str] = None,
|
command: Optional[str] = None,
|
||||||
@ -185,7 +185,7 @@ class DebugTask(Command):
|
|||||||
|
|
||||||
def _get_node(
|
def _get_node(
|
||||||
self, task_id: UUID_EXPANSION, node_id: Optional[UUID]
|
self, task_id: UUID_EXPANSION, node_id: Optional[UUID]
|
||||||
) -> Tuple[UUID, UUID]:
|
) -> Tuple[str, UUID]:
|
||||||
nodes = self.list_nodes(task_id)
|
nodes = self.list_nodes(task_id)
|
||||||
if not nodes:
|
if not nodes:
|
||||||
raise Exception("task is not currently executing on nodes")
|
raise Exception("task is not currently executing on nodes")
|
||||||
|
@ -186,7 +186,7 @@ def main() -> None:
|
|||||||
),
|
),
|
||||||
EventPoolDeleted(pool_name=PoolName("example")),
|
EventPoolDeleted(pool_name=PoolName("example")),
|
||||||
EventScalesetCreated(
|
EventScalesetCreated(
|
||||||
scaleset_id=UUID(int=0),
|
scaleset_id="example-000",
|
||||||
pool_name=PoolName("example"),
|
pool_name=PoolName("example"),
|
||||||
vm_sku="Standard_D2s_v3",
|
vm_sku="Standard_D2s_v3",
|
||||||
image="Canonical:0001-com-ubuntu-server-focal:20_04-lts:latest",
|
image="Canonical:0001-com-ubuntu-server-focal:20_04-lts:latest",
|
||||||
@ -194,20 +194,20 @@ def main() -> None:
|
|||||||
size=10,
|
size=10,
|
||||||
),
|
),
|
||||||
EventScalesetFailed(
|
EventScalesetFailed(
|
||||||
scaleset_id=UUID(int=0),
|
scaleset_id="example-000",
|
||||||
pool_name=PoolName("example"),
|
pool_name=PoolName("example"),
|
||||||
error=Error(
|
error=Error(
|
||||||
code=ErrorCode.UNABLE_TO_RESIZE, errors=["example error message"]
|
code=ErrorCode.UNABLE_TO_RESIZE, errors=["example error message"]
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
EventScalesetDeleted(scaleset_id=UUID(int=0), pool_name=PoolName("example")),
|
EventScalesetDeleted(scaleset_id="example-000", pool_name=PoolName("example")),
|
||||||
EventScalesetStateUpdated(
|
EventScalesetStateUpdated(
|
||||||
scaleset_id=UUID(int=0),
|
scaleset_id="example-000",
|
||||||
pool_name=PoolName("example"),
|
pool_name=PoolName("example"),
|
||||||
state=ScalesetState.init,
|
state=ScalesetState.init,
|
||||||
),
|
),
|
||||||
EventScalesetResizeScheduled(
|
EventScalesetResizeScheduled(
|
||||||
scaleset_id=UUID(int=0), pool_name=PoolName("example"), size=0
|
scaleset_id="example-000", pool_name=PoolName("example"), size=0
|
||||||
),
|
),
|
||||||
EventJobCreated(
|
EventJobCreated(
|
||||||
job_id=UUID(int=0),
|
job_id=UUID(int=0),
|
||||||
|
@ -98,7 +98,7 @@ class EventPing(BaseEvent, BaseResponse):
|
|||||||
|
|
||||||
|
|
||||||
class EventScalesetCreated(BaseEvent):
|
class EventScalesetCreated(BaseEvent):
|
||||||
scaleset_id: UUID
|
scaleset_id: str
|
||||||
pool_name: PoolName
|
pool_name: PoolName
|
||||||
vm_sku: str
|
vm_sku: str
|
||||||
image: str
|
image: str
|
||||||
@ -107,18 +107,18 @@ class EventScalesetCreated(BaseEvent):
|
|||||||
|
|
||||||
|
|
||||||
class EventScalesetFailed(BaseEvent):
|
class EventScalesetFailed(BaseEvent):
|
||||||
scaleset_id: UUID
|
scaleset_id: str
|
||||||
pool_name: PoolName
|
pool_name: PoolName
|
||||||
error: Error
|
error: Error
|
||||||
|
|
||||||
|
|
||||||
class EventScalesetDeleted(BaseEvent):
|
class EventScalesetDeleted(BaseEvent):
|
||||||
scaleset_id: UUID
|
scaleset_id: str
|
||||||
pool_name: PoolName
|
pool_name: PoolName
|
||||||
|
|
||||||
|
|
||||||
class EventScalesetResizeScheduled(BaseEvent):
|
class EventScalesetResizeScheduled(BaseEvent):
|
||||||
scaleset_id: UUID
|
scaleset_id: str
|
||||||
pool_name: PoolName
|
pool_name: PoolName
|
||||||
size: int
|
size: int
|
||||||
|
|
||||||
@ -159,32 +159,32 @@ class EventProxyStateUpdated(BaseEvent):
|
|||||||
|
|
||||||
class EventNodeCreated(BaseEvent):
|
class EventNodeCreated(BaseEvent):
|
||||||
machine_id: UUID
|
machine_id: UUID
|
||||||
scaleset_id: Optional[UUID]
|
scaleset_id: Optional[str]
|
||||||
pool_name: PoolName
|
pool_name: PoolName
|
||||||
|
|
||||||
|
|
||||||
class EventNodeHeartbeat(BaseEvent):
|
class EventNodeHeartbeat(BaseEvent):
|
||||||
machine_id: UUID
|
machine_id: UUID
|
||||||
scaleset_id: Optional[UUID]
|
scaleset_id: Optional[str]
|
||||||
pool_name: PoolName
|
pool_name: PoolName
|
||||||
machine_state: Optional[NodeState]
|
machine_state: Optional[NodeState]
|
||||||
|
|
||||||
|
|
||||||
class EventNodeDeleted(BaseEvent):
|
class EventNodeDeleted(BaseEvent):
|
||||||
machine_id: UUID
|
machine_id: UUID
|
||||||
scaleset_id: Optional[UUID]
|
scaleset_id: Optional[str]
|
||||||
pool_name: PoolName
|
pool_name: PoolName
|
||||||
|
|
||||||
|
|
||||||
class EventScalesetStateUpdated(BaseEvent):
|
class EventScalesetStateUpdated(BaseEvent):
|
||||||
scaleset_id: UUID
|
scaleset_id: str
|
||||||
pool_name: PoolName
|
pool_name: PoolName
|
||||||
state: ScalesetState
|
state: ScalesetState
|
||||||
|
|
||||||
|
|
||||||
class EventNodeStateUpdated(BaseEvent):
|
class EventNodeStateUpdated(BaseEvent):
|
||||||
machine_id: UUID
|
machine_id: UUID
|
||||||
scaleset_id: Optional[UUID]
|
scaleset_id: Optional[str]
|
||||||
pool_name: PoolName
|
pool_name: PoolName
|
||||||
state: NodeState
|
state: NodeState
|
||||||
|
|
||||||
|
@ -604,7 +604,7 @@ class Node(BaseModel):
|
|||||||
pool_id: Optional[UUID]
|
pool_id: Optional[UUID]
|
||||||
machine_id: UUID
|
machine_id: UUID
|
||||||
state: NodeState = Field(default=NodeState.init)
|
state: NodeState = Field(default=NodeState.init)
|
||||||
scaleset_id: Optional[UUID] = None
|
scaleset_id: Optional[str] = None
|
||||||
tasks: Optional[List[NodeTasks]] = None
|
tasks: Optional[List[NodeTasks]] = None
|
||||||
messages: Optional[List[NodeCommand]] = None
|
messages: Optional[List[NodeCommand]] = None
|
||||||
heartbeat: Optional[datetime]
|
heartbeat: Optional[datetime]
|
||||||
@ -615,7 +615,7 @@ class Node(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class ScalesetSummary(BaseModel):
|
class ScalesetSummary(BaseModel):
|
||||||
scaleset_id: UUID
|
scaleset_id: str
|
||||||
state: ScalesetState
|
state: ScalesetState
|
||||||
|
|
||||||
|
|
||||||
@ -668,7 +668,7 @@ class ScalesetNodeState(BaseModel):
|
|||||||
class Scaleset(BaseModel):
|
class Scaleset(BaseModel):
|
||||||
timestamp: Optional[datetime] = Field(alias="Timestamp")
|
timestamp: Optional[datetime] = Field(alias="Timestamp")
|
||||||
pool_name: PoolName
|
pool_name: PoolName
|
||||||
scaleset_id: UUID = Field(default_factory=uuid4)
|
scaleset_id: str
|
||||||
state: ScalesetState = Field(default=ScalesetState.init)
|
state: ScalesetState = Field(default=ScalesetState.init)
|
||||||
auth: Optional[Authentication]
|
auth: Optional[Authentication]
|
||||||
vm_sku: str
|
vm_sku: str
|
||||||
@ -686,7 +686,7 @@ class Scaleset(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class AutoScale(BaseModel):
|
class AutoScale(BaseModel):
|
||||||
scaleset_id: UUID
|
scaleset_id: str
|
||||||
min: int = Field(ge=0)
|
min: int = Field(ge=0)
|
||||||
max: int = Field(ge=1)
|
max: int = Field(ge=1)
|
||||||
default: int = Field(ge=0)
|
default: int = Field(ge=0)
|
||||||
@ -812,7 +812,7 @@ class TaskEventSummary(BaseModel):
|
|||||||
|
|
||||||
class NodeAssignment(BaseModel):
|
class NodeAssignment(BaseModel):
|
||||||
node_id: UUID
|
node_id: UUID
|
||||||
scaleset_id: Optional[UUID]
|
scaleset_id: Optional[str]
|
||||||
state: NodeTaskState
|
state: NodeTaskState
|
||||||
|
|
||||||
|
|
||||||
|
@ -91,7 +91,7 @@ class AgentRegistrationGet(BaseRequest):
|
|||||||
|
|
||||||
class AgentRegistrationPost(BaseRequest):
|
class AgentRegistrationPost(BaseRequest):
|
||||||
pool_name: PoolName
|
pool_name: PoolName
|
||||||
scaleset_id: Optional[UUID]
|
scaleset_id: Optional[str]
|
||||||
machine_id: UUID
|
machine_id: UUID
|
||||||
version: str = Field(default="1.0.0")
|
version: str = Field(default="1.0.0")
|
||||||
|
|
||||||
@ -122,7 +122,7 @@ class PoolStop(BaseRequest):
|
|||||||
|
|
||||||
|
|
||||||
class ProxyGet(BaseRequest):
|
class ProxyGet(BaseRequest):
|
||||||
scaleset_id: Optional[UUID]
|
scaleset_id: Optional[str]
|
||||||
machine_id: Optional[UUID]
|
machine_id: Optional[UUID]
|
||||||
dst_port: Optional[int]
|
dst_port: Optional[int]
|
||||||
|
|
||||||
@ -139,14 +139,14 @@ class ProxyGet(BaseRequest):
|
|||||||
|
|
||||||
|
|
||||||
class ProxyCreate(BaseRequest):
|
class ProxyCreate(BaseRequest):
|
||||||
scaleset_id: UUID
|
scaleset_id: str
|
||||||
machine_id: UUID
|
machine_id: UUID
|
||||||
dst_port: int
|
dst_port: int
|
||||||
duration: int = Field(ge=ONE_HOUR, le=SEVEN_DAYS)
|
duration: int = Field(ge=ONE_HOUR, le=SEVEN_DAYS)
|
||||||
|
|
||||||
|
|
||||||
class ProxyDelete(BaseRequest):
|
class ProxyDelete(BaseRequest):
|
||||||
scaleset_id: UUID
|
scaleset_id: str
|
||||||
machine_id: UUID
|
machine_id: UUID
|
||||||
dst_port: Optional[int]
|
dst_port: Optional[int]
|
||||||
|
|
||||||
@ -154,7 +154,7 @@ class ProxyDelete(BaseRequest):
|
|||||||
class NodeSearch(BaseRequest):
|
class NodeSearch(BaseRequest):
|
||||||
machine_id: Optional[UUID]
|
machine_id: Optional[UUID]
|
||||||
state: Optional[List[NodeState]]
|
state: Optional[List[NodeState]]
|
||||||
scaleset_id: Optional[UUID]
|
scaleset_id: Optional[str]
|
||||||
pool_name: Optional[PoolName]
|
pool_name: Optional[PoolName]
|
||||||
|
|
||||||
|
|
||||||
@ -168,18 +168,18 @@ class NodeUpdate(BaseRequest):
|
|||||||
|
|
||||||
|
|
||||||
class ScalesetSearch(BaseRequest):
|
class ScalesetSearch(BaseRequest):
|
||||||
scaleset_id: Optional[UUID]
|
scaleset_id: Optional[str]
|
||||||
state: Optional[List[ScalesetState]]
|
state: Optional[List[ScalesetState]]
|
||||||
include_auth: bool = Field(default=False)
|
include_auth: bool = Field(default=False)
|
||||||
|
|
||||||
|
|
||||||
class ScalesetStop(BaseRequest):
|
class ScalesetStop(BaseRequest):
|
||||||
scaleset_id: UUID
|
scaleset_id: str
|
||||||
now: bool
|
now: bool
|
||||||
|
|
||||||
|
|
||||||
class ScalesetUpdate(BaseRequest):
|
class ScalesetUpdate(BaseRequest):
|
||||||
scaleset_id: UUID
|
scaleset_id: str
|
||||||
size: Optional[int] = Field(ge=1)
|
size: Optional[int] = Field(ge=1)
|
||||||
|
|
||||||
|
|
||||||
|
@ -54,6 +54,7 @@ class TestScaleset(unittest.TestCase):
|
|||||||
def test_scaleset_size(self) -> None:
|
def test_scaleset_size(self) -> None:
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
Scaleset(
|
Scaleset(
|
||||||
|
scaleset_id="test-pool-000",
|
||||||
pool_name=PoolName("test-pool"),
|
pool_name=PoolName("test-pool"),
|
||||||
vm_sku="Standard_D2ds_v4",
|
vm_sku="Standard_D2ds_v4",
|
||||||
image="Canonical:0001-com-ubuntu-server-focal:20_04-lts:latest",
|
image="Canonical:0001-com-ubuntu-server-focal:20_04-lts:latest",
|
||||||
@ -63,6 +64,7 @@ class TestScaleset(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
scaleset = Scaleset(
|
scaleset = Scaleset(
|
||||||
|
scaleset_id="test-pool-000",
|
||||||
pool_name=PoolName("test-pool"),
|
pool_name=PoolName("test-pool"),
|
||||||
vm_sku="Standard_D2ds_v4",
|
vm_sku="Standard_D2ds_v4",
|
||||||
image="Canonical:0001-com-ubuntu-server-focal:20_04-lts:latest",
|
image="Canonical:0001-com-ubuntu-server-focal:20_04-lts:latest",
|
||||||
@ -73,6 +75,7 @@ class TestScaleset(unittest.TestCase):
|
|||||||
self.assertEqual(scaleset.size, 0)
|
self.assertEqual(scaleset.size, 0)
|
||||||
|
|
||||||
scaleset = Scaleset(
|
scaleset = Scaleset(
|
||||||
|
scaleset_id="test-pool-000",
|
||||||
pool_name=PoolName("test-pool"),
|
pool_name=PoolName("test-pool"),
|
||||||
vm_sku="Standard_D2ds_v4",
|
vm_sku="Standard_D2ds_v4",
|
||||||
image="Canonical:0001-com-ubuntu-server-focal:20_04-lts:latest",
|
image="Canonical:0001-com-ubuntu-server-focal:20_04-lts:latest",
|
||||||
|
Reference in New Issue
Block a user