mirror of
https://github.com/microsoft/onefuzz.git
synced 2025-06-14 11:08:06 +00:00
Extend use of validated string types (#2357)
In the Python code there were more validated string types that haven't been properly ported for use in the C# code (such as Region). Add that type back in and improve some others: - Use `Region` type to represent regions (implicitly convertible to/from the `AzureLocation` SDK type) - Improve validation of `Container` type to match Azure specs and use it in more places - Restore/fix validation of `PoolName` type which was previously removed (#2080) due to being too strict: now allows 1-64 ASCII alphanumeric/hyphen/dash - We want to restrict pool names so that we can use them as disambiguating prefixes for scaleset names (see #2189). Note that underscore is not actually permitted in scaleset names so we will probably end up mapping it to hyphen. Note that once C#7 lands we will be able to simplify the usage of `ValidatedString` a lot (using static abstract methods). ---- Open questions: For deserializing from "known-good" places such as table storage or from Azure SDK APIs, should we have an `T.UnsafeAssumeValid(string input)` method which does no validation, to protect us from breakage?
This commit is contained in:
@ -42,7 +42,7 @@ public class ContainersFunction {
|
|||||||
new Error(
|
new Error(
|
||||||
Code: ErrorCode.INVALID_REQUEST,
|
Code: ErrorCode.INVALID_REQUEST,
|
||||||
Errors: new[] { "invalid container" }),
|
Errors: new[] { "invalid container" }),
|
||||||
context: get.Name.ContainerName);
|
context: get.Name.String);
|
||||||
}
|
}
|
||||||
|
|
||||||
var metadata = (await container.GetPropertiesAsync()).Value.Metadata;
|
var metadata = (await container.GetPropertiesAsync()).Value.Metadata;
|
||||||
@ -63,7 +63,7 @@ public class ContainersFunction {
|
|||||||
|
|
||||||
// otherwise list all containers
|
// otherwise list all containers
|
||||||
var containers = await _context.Containers.GetContainers(StorageType.Corpus);
|
var containers = await _context.Containers.GetContainers(StorageType.Corpus);
|
||||||
var result = containers.Select(c => new ContainerInfoBase(new Container(c.Key), c.Value));
|
var result = containers.Select(c => new ContainerInfoBase(c.Key, c.Value));
|
||||||
return await RequestHandling.Ok(req, result);
|
return await RequestHandling.Ok(req, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,7 +104,7 @@ public class ContainersFunction {
|
|||||||
new Error(
|
new Error(
|
||||||
Code: ErrorCode.INVALID_REQUEST,
|
Code: ErrorCode.INVALID_REQUEST,
|
||||||
Errors: new[] { "invalid container" }),
|
Errors: new[] { "invalid container" }),
|
||||||
context: post.Name.ContainerName);
|
context: post.Name.String);
|
||||||
}
|
}
|
||||||
|
|
||||||
return await RequestHandling.Ok(
|
return await RequestHandling.Ok(
|
||||||
|
@ -21,13 +21,13 @@ public class Download {
|
|||||||
private async Async.Task<HttpResponseData> Get(HttpRequestData req) {
|
private async Async.Task<HttpResponseData> Get(HttpRequestData req) {
|
||||||
var query = HttpUtility.ParseQueryString(req.Url.Query);
|
var query = HttpUtility.ParseQueryString(req.Url.Query);
|
||||||
|
|
||||||
var container = query["container"];
|
var queryContainer = query["container"];
|
||||||
if (container is null) {
|
if (queryContainer is null || !Container.TryParse(queryContainer, out var container)) {
|
||||||
return await _context.RequestHandling.NotOk(
|
return await _context.RequestHandling.NotOk(
|
||||||
req,
|
req,
|
||||||
new Error(
|
new Error(
|
||||||
ErrorCode.INVALID_REQUEST,
|
ErrorCode.INVALID_REQUEST,
|
||||||
new string[] { "'container' query parameter must be provided" }),
|
new string[] { "'container' query parameter must be provided and valid" }),
|
||||||
"download");
|
"download");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,7 +42,7 @@ public class Download {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var sasUri = await _context.Containers.GetFileSasUrl(
|
var sasUri = await _context.Containers.GetFileSasUrl(
|
||||||
new Container(container),
|
container,
|
||||||
filename,
|
filename,
|
||||||
StorageType.Corpus,
|
StorageType.Corpus,
|
||||||
BlobSasPermissions.Read,
|
BlobSasPermissions.Read,
|
||||||
|
@ -52,7 +52,7 @@ public class Jobs {
|
|||||||
var metadata = new Dictionary<string, string>{
|
var metadata = new Dictionary<string, string>{
|
||||||
{ "container_type", "logs" }, // TODO: use ContainerType.Logs enum somehow; needs snake case name
|
{ "container_type", "logs" }, // TODO: use ContainerType.Logs enum somehow; needs snake case name
|
||||||
};
|
};
|
||||||
var containerName = new Container($"logs-{job.JobId}");
|
var containerName = Container.Parse($"logs-{job.JobId}");
|
||||||
var containerSas = await _context.Containers.CreateContainer(containerName, StorageType.Corpus, metadata);
|
var containerSas = await _context.Containers.CreateContainer(containerName, StorageType.Corpus, metadata);
|
||||||
if (containerSas is null) {
|
if (containerSas is null) {
|
||||||
return await _context.RequestHandling.NotOk(
|
return await _context.RequestHandling.NotOk(
|
||||||
|
@ -22,8 +22,9 @@ public class Notifications {
|
|||||||
return await _context.RequestHandling.NotOk(req, request.ErrorV, "notification search");
|
return await _context.RequestHandling.NotOk(req, request.ErrorV, "notification search");
|
||||||
}
|
}
|
||||||
|
|
||||||
var entries = request.OkV switch { { Container: null } => _context.NotificationOperations.SearchAll(), { Container: var c } => _context.NotificationOperations.SearchByRowKeys(c.Select(x => x.ContainerName))
|
var entries = request.OkV switch { { Container: null } => _context.NotificationOperations.SearchAll(), { Container: var c } => _context.NotificationOperations.SearchByRowKeys(c.Select(x => x.String))
|
||||||
};
|
};
|
||||||
|
|
||||||
var response = req.CreateResponse(HttpStatusCode.OK);
|
var response = req.CreateResponse(HttpStatusCode.OK);
|
||||||
await response.WriteAsJsonAsync(entries);
|
await response.WriteAsJsonAsync(entries);
|
||||||
return response;
|
return response;
|
||||||
|
@ -56,6 +56,6 @@ public class QueueFileChanges {
|
|||||||
var path = string.Join('/', parts.Skip(1));
|
var path = string.Join('/', parts.Skip(1));
|
||||||
|
|
||||||
log.Info($"file added container: {container} - path: {path}");
|
log.Info($"file added container: {container} - path: {path}");
|
||||||
await _notificationOperations.NewFiles(new Container(container), path, failTaskOnTransientError);
|
await _notificationOperations.NewFiles(Container.Parse(container), path, failTaskOnTransientError);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -75,7 +75,7 @@ public class Scaleset {
|
|||||||
context: "ScalesetCreate");
|
context: "ScalesetCreate");
|
||||||
}
|
}
|
||||||
|
|
||||||
string region;
|
Region region;
|
||||||
if (create.Region is null) {
|
if (create.Region is null) {
|
||||||
region = await _context.Creds.GetBaseRegion();
|
region = await _context.Creds.GetBaseRegion();
|
||||||
} else {
|
} else {
|
||||||
|
@ -62,15 +62,16 @@ public class TimerProxy {
|
|||||||
// nsg enabled OneFuzz this will overwrite existing NSG
|
// nsg enabled OneFuzz this will overwrite existing NSG
|
||||||
// assignment though. This behavior is acceptable at this point
|
// assignment though. This behavior is acceptable at this point
|
||||||
// since we do not support bring your own NSG
|
// since we do not support bring your own NSG
|
||||||
|
var nsgName = Nsg.NameFromRegion(region);
|
||||||
|
|
||||||
if (await nsgOpertions.GetNsg(region) != null) {
|
if (await nsgOpertions.GetNsg(nsgName) != null) {
|
||||||
var network = await Network.Init(region, _context);
|
var network = await Network.Init(region, _context);
|
||||||
|
|
||||||
var subnet = await network.GetSubnet();
|
var subnet = await network.GetSubnet();
|
||||||
if (subnet != null) {
|
if (subnet != null) {
|
||||||
var vnet = await network.GetVnet();
|
var vnet = await network.GetVnet();
|
||||||
if (vnet != null) {
|
if (vnet != null) {
|
||||||
var result = await nsgOpertions.AssociateSubnet(region, vnet, subnet);
|
var result = await nsgOpertions.AssociateSubnet(nsgName, vnet, subnet);
|
||||||
if (!result.OkV) {
|
if (!result.OkV) {
|
||||||
_logger.Error($"Failed to associate NSG and subnet due to {result.ErrorV} in region {region}");
|
_logger.Error($"Failed to associate NSG and subnet due to {result.ErrorV} in region {region}");
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
using Microsoft.OneFuzz.Service.OneFuzzLib.Orm;
|
using Microsoft.OneFuzz.Service.OneFuzzLib.Orm;
|
||||||
using Region = System.String;
|
|
||||||
|
|
||||||
namespace Microsoft.OneFuzz.Service;
|
namespace Microsoft.OneFuzz.Service;
|
||||||
|
|
||||||
|
@ -5,7 +5,6 @@ using Microsoft.OneFuzz.Service.OneFuzzLib.Orm;
|
|||||||
using Endpoint = System.String;
|
using Endpoint = System.String;
|
||||||
using GroupId = System.Guid;
|
using GroupId = System.Guid;
|
||||||
using PrincipalId = System.Guid;
|
using PrincipalId = System.Guid;
|
||||||
using Region = System.String;
|
|
||||||
|
|
||||||
namespace Microsoft.OneFuzz.Service;
|
namespace Microsoft.OneFuzz.Service;
|
||||||
|
|
||||||
@ -406,25 +405,6 @@ public record Scaleset(
|
|||||||
// '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);
|
||||||
|
|
||||||
[JsonConverter(typeof(ContainerConverter))]
|
|
||||||
public record Container(string ContainerName) {
|
|
||||||
public string ContainerName { get; } = ContainerName.All(c => char.IsLetterOrDigit(c) || c == '-') ? ContainerName : throw new ArgumentException("Container name must have only numbers, letters or dashes");
|
|
||||||
public override string ToString() {
|
|
||||||
return ContainerName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class ContainerConverter : JsonConverter<Container> {
|
|
||||||
public override Container? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {
|
|
||||||
var containerName = reader.GetString();
|
|
||||||
return containerName == null ? null : new Container(containerName);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Write(Utf8JsonWriter writer, Container value, JsonSerializerOptions options) {
|
|
||||||
writer.WriteStringValue(value.ContainerName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public record Notification(
|
public record Notification(
|
||||||
[PartitionKey] Guid NotificationId,
|
[PartitionKey] Guid NotificationId,
|
||||||
[RowKey] Container Container,
|
[RowKey] Container Container,
|
||||||
@ -732,7 +712,14 @@ public record Job(
|
|||||||
public UserInfo? UserInfo { get; set; }
|
public UserInfo? UserInfo { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public record Nsg(string Name, Region Region);
|
public record Nsg(string Name, Region Region) {
|
||||||
|
public static Nsg ForRegion(Region region)
|
||||||
|
=> new(NameFromRegion(region), region);
|
||||||
|
|
||||||
|
// Currently, the name of a NSG is the same as the region it is in.
|
||||||
|
public static string NameFromRegion(Region region)
|
||||||
|
=> region.String;
|
||||||
|
};
|
||||||
|
|
||||||
public record WorkUnit(
|
public record WorkUnit(
|
||||||
Guid JobId,
|
Guid JobId,
|
||||||
|
@ -191,7 +191,7 @@ public record ScalesetCreate(
|
|||||||
[property: Required] PoolName PoolName,
|
[property: Required] PoolName PoolName,
|
||||||
[property: Required] string VmSku,
|
[property: Required] string VmSku,
|
||||||
[property: Required] string Image,
|
[property: Required] string Image,
|
||||||
string? Region,
|
Region? Region,
|
||||||
[property: Range(1, long.MaxValue), Required] long Size,
|
[property: Range(1, long.MaxValue), Required] long Size,
|
||||||
[property: Required] bool SpotInstances,
|
[property: Required] bool SpotInstances,
|
||||||
[property: Required] Dictionary<string, string> Tags,
|
[property: Required] Dictionary<string, string> Tags,
|
||||||
|
@ -56,7 +56,7 @@ public record BoolResult(
|
|||||||
|
|
||||||
public record InfoResponse(
|
public record InfoResponse(
|
||||||
string ResourceGroup,
|
string ResourceGroup,
|
||||||
string Region,
|
Region Region,
|
||||||
string Subscription,
|
string Subscription,
|
||||||
IReadOnlyDictionary<string, InfoVersion> Versions,
|
IReadOnlyDictionary<string, InfoVersion> Versions,
|
||||||
Guid? InstanceId,
|
Guid? InstanceId,
|
||||||
@ -127,7 +127,7 @@ public record ScalesetResponse(
|
|||||||
Authentication? Auth,
|
Authentication? Auth,
|
||||||
string VmSku,
|
string VmSku,
|
||||||
string Image,
|
string Image,
|
||||||
string Region,
|
Region Region,
|
||||||
long Size,
|
long Size,
|
||||||
bool? SpotInstances,
|
bool? SpotInstances,
|
||||||
bool EmphemeralOsDisks,
|
bool EmphemeralOsDisks,
|
||||||
@ -175,7 +175,7 @@ public record ProxyGetResult(
|
|||||||
);
|
);
|
||||||
|
|
||||||
public record ProxyInfo(
|
public record ProxyInfo(
|
||||||
string Region,
|
Region Region,
|
||||||
Guid ProxyId,
|
Guid ProxyId,
|
||||||
VmState State
|
VmState State
|
||||||
);
|
);
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
using Azure.Core;
|
||||||
|
|
||||||
namespace Microsoft.OneFuzz.Service;
|
namespace Microsoft.OneFuzz.Service;
|
||||||
|
|
||||||
@ -11,6 +13,10 @@ static class Check {
|
|||||||
|
|
||||||
private static readonly Regex _isAlnumDash = new(@"\A[a-zA-Z0-9\-]+\z", RegexOptions.Compiled);
|
private static readonly Regex _isAlnumDash = new(@"\A[a-zA-Z0-9\-]+\z", RegexOptions.Compiled);
|
||||||
public static bool IsAlnumDash(string input) => _isAlnumDash.IsMatch(input);
|
public static bool IsAlnumDash(string input) => _isAlnumDash.IsMatch(input);
|
||||||
|
|
||||||
|
// Permits 1-64 characters: alphanumeric, underscore, or dash.
|
||||||
|
private static readonly Regex _isNameLike = new(@"\A[_a-zA-Z0-9\-]{1,64}\z", RegexOptions.Compiled);
|
||||||
|
public static bool IsNameLike(string input) => _isNameLike.IsMatch(input);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Base class for types that are wrappers around a validated string.
|
// Base class for types that are wrappers around a validated string.
|
||||||
@ -47,9 +53,11 @@ public abstract class ValidatedStringConverter<T> : JsonConverter<T> where T : V
|
|||||||
}
|
}
|
||||||
|
|
||||||
[JsonConverter(typeof(Converter))]
|
[JsonConverter(typeof(Converter))]
|
||||||
public record PoolName : ValidatedString {
|
public sealed record PoolName : ValidatedString {
|
||||||
public PoolName(string value) : base(value) {
|
private static bool IsValid(string input) => Check.IsNameLike(input);
|
||||||
// Debug.Assert(Check.IsAlnumDash(value));
|
|
||||||
|
private PoolName(string value) : base(value) {
|
||||||
|
Debug.Assert(IsValid(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static PoolName Parse(string input) {
|
public static PoolName Parse(string input) {
|
||||||
@ -61,14 +69,10 @@ public record PoolName : ValidatedString {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static bool TryParse(string input, [NotNullWhen(returnValue: true)] out PoolName? result) {
|
public static bool TryParse(string input, [NotNullWhen(returnValue: true)] out PoolName? result) {
|
||||||
|
if (!IsValid(input)) {
|
||||||
// bypassing the validation because this code has a stricter validation than the python equivalent
|
result = default;
|
||||||
// see (issue #2080)
|
return false;
|
||||||
|
}
|
||||||
// if (!Check.IsAlnumDash(input)) {
|
|
||||||
// result = default;
|
|
||||||
// return false;
|
|
||||||
// }
|
|
||||||
|
|
||||||
result = new PoolName(input);
|
result = new PoolName(input);
|
||||||
return true;
|
return true;
|
||||||
@ -80,12 +84,12 @@ public record PoolName : ValidatedString {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* TODO: to be enabled in a separate PR
|
|
||||||
|
|
||||||
[JsonConverter(typeof(Converter))]
|
[JsonConverter(typeof(Converter))]
|
||||||
public record Region : ValidatedString {
|
public record Region : ValidatedString {
|
||||||
private Region(string value) : base(value) {
|
private static bool IsValid(string input) => Check.IsAlnum(input);
|
||||||
Debug.Assert(Check.IsAlnum(value));
|
|
||||||
|
private Region(string value) : base(value.ToLowerInvariant()) {
|
||||||
|
Debug.Assert(IsValid(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Region Parse(string input) {
|
public static Region Parse(string input) {
|
||||||
@ -93,11 +97,11 @@ public record Region : ValidatedString {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new ArgumentException("Region name must have only numbers, letters or dashes");
|
throw new ArgumentException("Region name must have only numbers or letters");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool TryParse(string input, [NotNullWhen(returnValue: true)] out Region? result) {
|
public static bool TryParse(string input, [NotNullWhen(returnValue: true)] out Region? result) {
|
||||||
if (!Check.IsAlnum(input)) {
|
if (!IsValid(input)) {
|
||||||
result = default;
|
result = default;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -106,6 +110,9 @@ public record Region : ValidatedString {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static implicit operator AzureLocation(Region me) => new(me.String);
|
||||||
|
public static implicit operator Region(AzureLocation it) => new(it.Name);
|
||||||
|
|
||||||
public sealed class Converter : ValidatedStringConverter<Region> {
|
public sealed class Converter : ValidatedStringConverter<Region> {
|
||||||
protected override bool TryParse(string input, out Region? output)
|
protected override bool TryParse(string input, out Region? output)
|
||||||
=> Region.TryParse(input, out output);
|
=> Region.TryParse(input, out output);
|
||||||
@ -114,8 +121,16 @@ public record Region : ValidatedString {
|
|||||||
|
|
||||||
[JsonConverter(typeof(Converter))]
|
[JsonConverter(typeof(Converter))]
|
||||||
public record Container : ValidatedString {
|
public record Container : ValidatedString {
|
||||||
|
// See: https://docs.microsoft.com/en-us/azure/azure-resource-manager/management/resource-name-rules#microsoftstorage
|
||||||
|
// - 3-63
|
||||||
|
// - Lowercase letters, numbers, and hyphens.
|
||||||
|
// - Start with lowercase letter or number. Can't use consecutive hyphens.
|
||||||
|
private static readonly Regex _containerRegex = new(@"\A(?!-)(?!.*--)[a-z0-9\-]{3,63}\z", RegexOptions.Compiled);
|
||||||
|
|
||||||
|
private static bool IsValid(string input) => _containerRegex.IsMatch(input);
|
||||||
|
|
||||||
private Container(string value) : base(value) {
|
private Container(string value) : base(value) {
|
||||||
Debug.Assert(Check.IsAlnumDash(value));
|
Debug.Assert(IsValid(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Container Parse(string input) {
|
public static Container Parse(string input) {
|
||||||
@ -127,7 +142,7 @@ public record Container : ValidatedString {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static bool TryParse(string input, [NotNullWhen(returnValue: true)] out Container? result) {
|
public static bool TryParse(string input, [NotNullWhen(returnValue: true)] out Container? result) {
|
||||||
if (!Check.IsAlnumDash(input)) {
|
if (!IsValid(input)) {
|
||||||
result = default;
|
result = default;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -141,4 +156,3 @@ public record Container : ValidatedString {
|
|||||||
=> Container.TryParse(input, out output);
|
=> Container.TryParse(input, out output);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
@ -49,7 +49,7 @@ namespace ApiService.TestHooks {
|
|||||||
_log.Info("Get base region");
|
_log.Info("Get base region");
|
||||||
var resp = req.CreateResponse(HttpStatusCode.OK);
|
var resp = req.CreateResponse(HttpStatusCode.OK);
|
||||||
var region = await _creds.GetBaseRegion();
|
var region = await _creds.GetBaseRegion();
|
||||||
await resp.WriteStringAsync(region);
|
await resp.WriteStringAsync(region.String);
|
||||||
return resp;
|
return resp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ namespace ApiService.TestHooks {
|
|||||||
var fileName = query["fileName"];
|
var fileName = query["fileName"];
|
||||||
var failTaskOnTransientError = UriExtension.GetBool("failTaskOnTransientError", query, true);
|
var failTaskOnTransientError = UriExtension.GetBool("failTaskOnTransientError", query, true);
|
||||||
|
|
||||||
await _notificationOps.NewFiles(new Container(container), fileName, failTaskOnTransientError);
|
await _notificationOps.NewFiles(Container.Parse(container), fileName, failTaskOnTransientError);
|
||||||
var resp = req.CreateResponse(HttpStatusCode.OK);
|
var resp = req.CreateResponse(HttpStatusCode.OK);
|
||||||
return resp;
|
return resp;
|
||||||
}
|
}
|
||||||
@ -43,7 +43,7 @@ namespace ApiService.TestHooks {
|
|||||||
|
|
||||||
var query = UriExtension.GetQueryComponents(req.Url);
|
var query = UriExtension.GetQueryComponents(req.Url);
|
||||||
var container = query["container"];
|
var container = query["container"];
|
||||||
var notifications = _notificationOps.GetNotifications(new Container(container));
|
var notifications = _notificationOps.GetNotifications(Container.Parse(container));
|
||||||
|
|
||||||
var json = JsonSerializer.Serialize(await notifications.ToListAsync(), EntityConverter.GetJsonSerializerOptions());
|
var json = JsonSerializer.Serialize(await notifications.ToListAsync(), EntityConverter.GetJsonSerializerOptions());
|
||||||
var resp = req.CreateResponse(HttpStatusCode.OK);
|
var resp = req.CreateResponse(HttpStatusCode.OK);
|
||||||
|
@ -28,7 +28,7 @@ namespace ApiService.TestHooks {
|
|||||||
|
|
||||||
var poolRes = _proxyForward.SearchForward(
|
var poolRes = _proxyForward.SearchForward(
|
||||||
UriExtension.GetGuid("scaleSetId", query),
|
UriExtension.GetGuid("scaleSetId", query),
|
||||||
UriExtension.GetString("region", query),
|
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),
|
||||||
UriExtension.GetInt("dstPort", query));
|
UriExtension.GetInt("dstPort", query));
|
||||||
|
@ -105,7 +105,7 @@ public class AutoScaleOperations : Orm<AutoScale>, IAutoScaleOperations {
|
|||||||
|
|
||||||
return OneFuzzResultVoid.Ok;
|
return OneFuzzResultVoid.Ok;
|
||||||
}
|
}
|
||||||
private async Async.Task<OneFuzzResult<AutoscaleSettingResource>> CreateAutoScaleResourceFor(Guid resourceId, string location, AutoscaleProfile profile) {
|
private async Async.Task<OneFuzzResult<AutoscaleSettingResource>> CreateAutoScaleResourceFor(Guid resourceId, Region location, AutoscaleProfile profile) {
|
||||||
_logTracer.Info($"Creating auto-scale resource for: {resourceId}");
|
_logTracer.Info($"Creating auto-scale resource for: {resourceId}");
|
||||||
|
|
||||||
var resourceGroup = _context.Creds.GetBaseResourceGroup();
|
var resourceGroup = _context.Creds.GetBaseResourceGroup();
|
||||||
|
@ -458,21 +458,24 @@ public class Config : IConfig {
|
|||||||
return ResultVoid<TaskConfigError>.Ok();
|
return ResultVoid<TaskConfigError>.Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
var exist = new HashSet<string>();
|
var exist = new HashSet<Container>();
|
||||||
var containers = new Dictionary<ContainerType, List<Container>>();
|
var containers = new Dictionary<ContainerType, List<Container>>();
|
||||||
|
|
||||||
foreach (var container in config.Containers) {
|
foreach (var container in config.Containers) {
|
||||||
if (exist.Contains(container.Name.ContainerName)) {
|
if (exist.Contains(container.Name)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (await _containers.FindContainer(container.Name, StorageType.Corpus) == null) {
|
if (await _containers.FindContainer(container.Name, StorageType.Corpus) == null) {
|
||||||
return ResultVoid<TaskConfigError>.Error(new TaskConfigError($"missing container: {container.Name}"));
|
return ResultVoid<TaskConfigError>.Error(new TaskConfigError($"missing container: {container.Name}"));
|
||||||
}
|
}
|
||||||
exist.Add(container.Name.ContainerName);
|
|
||||||
|
exist.Add(container.Name);
|
||||||
|
|
||||||
if (!containers.ContainsKey(container.Type)) {
|
if (!containers.ContainsKey(container.Type)) {
|
||||||
containers.Add(container.Type, new List<Container>());
|
containers.Add(container.Type, new List<Container>());
|
||||||
}
|
}
|
||||||
|
|
||||||
containers[container.Type].Add(container.Name);
|
containers[container.Type].Add(container.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ public interface IContainers {
|
|||||||
public Async.Task<bool> BlobExists(Container container, string name, StorageType storageType);
|
public Async.Task<bool> BlobExists(Container container, string name, StorageType storageType);
|
||||||
|
|
||||||
public Async.Task<Uri> AddContainerSasUrl(Uri uri, TimeSpan? duration = null);
|
public Async.Task<Uri> AddContainerSasUrl(Uri uri, TimeSpan? duration = null);
|
||||||
public Async.Task<Dictionary<string, IDictionary<string, string>>> GetContainers(StorageType corpus);
|
public Async.Task<Dictionary<Container, IDictionary<string, string>>> GetContainers(StorageType corpus);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Containers : IContainers {
|
public class Containers : IContainers {
|
||||||
@ -36,7 +36,7 @@ public class Containers : IContainers {
|
|||||||
private readonly IStorage _storage;
|
private readonly IStorage _storage;
|
||||||
private readonly IServiceConfig _config;
|
private readonly IServiceConfig _config;
|
||||||
|
|
||||||
static TimeSpan CONTAINER_SAS_DEFAULT_DURATION = TimeSpan.FromDays(30);
|
static readonly TimeSpan CONTAINER_SAS_DEFAULT_DURATION = TimeSpan.FromDays(30);
|
||||||
|
|
||||||
public Containers(ILogTracer log, IStorage storage, IServiceConfig config) {
|
public Containers(ILogTracer log, IStorage storage, IServiceConfig config) {
|
||||||
_log = log;
|
_log = log;
|
||||||
@ -44,7 +44,7 @@ public class Containers : IContainers {
|
|||||||
_config = config;
|
_config = config;
|
||||||
|
|
||||||
_getInstanceId = new Lazy<Async.Task<Guid>>(async () => {
|
_getInstanceId = new Lazy<Async.Task<Guid>>(async () => {
|
||||||
var blob = await GetBlob(new Container("base-config"), "instance_id", StorageType.Config);
|
var blob = await GetBlob(WellKnownContainers.BaseConfig, "instance_id", StorageType.Config);
|
||||||
if (blob == null) {
|
if (blob == null) {
|
||||||
throw new Exception("Blob Not Found");
|
throw new Exception("Blob Not Found");
|
||||||
}
|
}
|
||||||
@ -99,14 +99,14 @@ public class Containers : IContainers {
|
|||||||
|
|
||||||
var account = _storage.ChooseAccount(storageType);
|
var account = _storage.ChooseAccount(storageType);
|
||||||
var client = await _storage.GetBlobServiceClientForAccount(account);
|
var client = await _storage.GetBlobServiceClientForAccount(account);
|
||||||
var containerName = _config.OneFuzzStoragePrefix + container.ContainerName;
|
var containerName = _config.OneFuzzStoragePrefix + container;
|
||||||
var cc = client.GetBlobContainerClient(containerName);
|
var cc = client.GetBlobContainerClient(containerName);
|
||||||
try {
|
try {
|
||||||
await cc.CreateAsync(metadata: metadata);
|
await cc.CreateAsync(metadata: metadata);
|
||||||
} catch (RequestFailedException ex) when (ex.ErrorCode == "ContainerAlreadyExists") {
|
} catch (RequestFailedException ex) when (ex.ErrorCode == "ContainerAlreadyExists") {
|
||||||
// note: resource exists error happens during creation if the container
|
// note: resource exists error happens during creation if the container
|
||||||
// is being deleted
|
// is being deleted
|
||||||
_log.Error($"unable to create container. account: {account} container: {container.ContainerName} metadata: {metadata} - {ex.Message}");
|
_log.Error($"unable to create container. account: {account} container: {container} metadata: {metadata} - {ex.Message}");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,7 +123,7 @@ public class Containers : IContainers {
|
|||||||
// # Secondary accounts, if they exist, are preferred for containers and have
|
// # Secondary accounts, if they exist, are preferred for containers and have
|
||||||
// # increased IOP rates, this should be a slight optimization
|
// # increased IOP rates, this should be a slight optimization
|
||||||
|
|
||||||
var containerName = _config.OneFuzzStoragePrefix + container.ContainerName;
|
var containerName = _config.OneFuzzStoragePrefix + container;
|
||||||
|
|
||||||
foreach (var account in _storage.GetAccounts(storageType).Reverse()) {
|
foreach (var account in _storage.GetAccounts(storageType).Reverse()) {
|
||||||
var accountClient = await _storage.GetBlobServiceClientForAccount(account);
|
var accountClient = await _storage.GetBlobServiceClientForAccount(account);
|
||||||
@ -137,7 +137,7 @@ public class Containers : IContainers {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async Async.Task<Uri> GetFileSasUrl(Container container, string name, StorageType storageType, BlobSasPermissions permissions, TimeSpan? duration = null) {
|
public async Async.Task<Uri> GetFileSasUrl(Container container, string name, StorageType storageType, BlobSasPermissions permissions, TimeSpan? duration = null) {
|
||||||
var client = await FindContainer(container, storageType) ?? throw new Exception($"unable to find container: {container.ContainerName} - {storageType}");
|
var client = await FindContainer(container, storageType) ?? throw new Exception($"unable to find container: {container} - {storageType}");
|
||||||
var blobClient = client.GetBlobClient(name);
|
var blobClient = client.GetBlobClient(name);
|
||||||
var timeWindow = SasTimeWindow(duration ?? TimeSpan.FromDays(30));
|
var timeWindow = SasTimeWindow(duration ?? TimeSpan.FromDays(30));
|
||||||
return _storage.GenerateBlobSasUri(permissions, blobClient, timeWindow);
|
return _storage.GenerateBlobSasUri(permissions, blobClient, timeWindow);
|
||||||
@ -160,7 +160,7 @@ public class Containers : IContainers {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async Async.Task SaveBlob(Container container, string name, string data, StorageType storageType) {
|
public async Async.Task SaveBlob(Container container, string name, string data, StorageType storageType) {
|
||||||
var client = await FindContainer(container, storageType) ?? throw new Exception($"unable to find container: {container.ContainerName} - {storageType}");
|
var client = await FindContainer(container, storageType) ?? throw new Exception($"unable to find container: {container} - {storageType}");
|
||||||
await client.GetBlobClient(name).UploadAsync(new BinaryData(data), overwrite: true);
|
await client.GetBlobClient(name).UploadAsync(new BinaryData(data), overwrite: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -192,23 +192,25 @@ public class Containers : IContainers {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Uri> GetContainerSasUrl(Container container, StorageType storageType, BlobContainerSasPermissions permissions, TimeSpan? duration = null) {
|
public async Task<Uri> GetContainerSasUrl(Container container, StorageType storageType, BlobContainerSasPermissions permissions, TimeSpan? duration = null) {
|
||||||
var client = await FindContainer(container, storageType) ?? throw new Exception($"unable to find container: {container.ContainerName} - {storageType}");
|
var client = await FindContainer(container, storageType) ?? throw new Exception($"unable to find container: {container} - {storageType}");
|
||||||
var timeWindow = SasTimeWindow(duration ?? CONTAINER_SAS_DEFAULT_DURATION);
|
var timeWindow = SasTimeWindow(duration ?? CONTAINER_SAS_DEFAULT_DURATION);
|
||||||
return _storage.GenerateBlobContainerSasUri(permissions, client, timeWindow);
|
return _storage.GenerateBlobContainerSasUri(permissions, client, timeWindow);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Async.Task<bool> BlobExists(Container container, string name, StorageType storageType) {
|
public async Async.Task<bool> BlobExists(Container container, string name, StorageType storageType) {
|
||||||
var client = await FindContainer(container, storageType) ?? throw new Exception($"unable to find container: {container.ContainerName} - {storageType}");
|
var client = await FindContainer(container, storageType) ?? throw new Exception($"unable to find container: {container} - {storageType}");
|
||||||
return await client.GetBlobClient(name).ExistsAsync();
|
return await client.GetBlobClient(name).ExistsAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Dictionary<string, IDictionary<string, string>>> GetContainers(StorageType corpus) {
|
public async Task<Dictionary<Container, IDictionary<string, string>>> GetContainers(StorageType corpus) {
|
||||||
var accounts = _storage.GetAccounts(corpus);
|
var accounts = _storage.GetAccounts(corpus);
|
||||||
IEnumerable<IEnumerable<KeyValuePair<string, IDictionary<string, string>>>> data =
|
IEnumerable<IEnumerable<KeyValuePair<Container, IDictionary<string, string>>>> data =
|
||||||
await Async.Task.WhenAll(accounts.Select(async acc => {
|
await Async.Task.WhenAll(accounts.Select(async acc => {
|
||||||
var service = await _storage.GetBlobServiceClientForAccount(acc);
|
var service = await _storage.GetBlobServiceClientForAccount(acc);
|
||||||
return await service.GetBlobContainersAsync(BlobContainerTraits.Metadata).Select(container =>
|
return await service
|
||||||
KeyValuePair.Create(container.Name, container.Properties.Metadata)).ToListAsync();
|
.GetBlobContainersAsync(BlobContainerTraits.Metadata)
|
||||||
|
.Select(container => KeyValuePair.Create(Container.Parse(container.Name), container.Properties.Metadata))
|
||||||
|
.ToListAsync();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return new(data.SelectMany(x => x));
|
return new(data.SelectMany(x => x));
|
||||||
|
@ -25,14 +25,14 @@ public interface ICreds {
|
|||||||
|
|
||||||
public SubscriptionResource GetSubscriptionResource();
|
public SubscriptionResource GetSubscriptionResource();
|
||||||
|
|
||||||
public Async.Task<string> GetBaseRegion();
|
public Async.Task<Region> GetBaseRegion();
|
||||||
|
public Async.Task<IReadOnlyList<Region>> GetRegions();
|
||||||
|
|
||||||
public Uri GetInstanceUrl();
|
public Uri GetInstanceUrl();
|
||||||
public Async.Task<Guid> GetScalesetPrincipalId();
|
public Async.Task<Guid> GetScalesetPrincipalId();
|
||||||
public GenericResource ParseResourceId(string resourceId);
|
public GenericResource ParseResourceId(string resourceId);
|
||||||
public GenericResource ParseResourceId(ResourceIdentifier resourceId);
|
public GenericResource ParseResourceId(ResourceIdentifier resourceId);
|
||||||
public Async.Task<GenericResource> GetData(GenericResource resource);
|
public Async.Task<GenericResource> GetData(GenericResource resource);
|
||||||
Async.Task<IReadOnlyList<string>> GetRegions();
|
|
||||||
public ResourceIdentifier GetScalesetIdentityResourcePath();
|
public ResourceIdentifier GetScalesetIdentityResourcePath();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,13 +95,13 @@ public sealed class Creds : ICreds {
|
|||||||
return ArmClient.GetSubscriptionResource(id);
|
return ArmClient.GetSubscriptionResource(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Async.Task<string> GetBaseRegion() {
|
public Async.Task<Region> GetBaseRegion() {
|
||||||
return _cache.GetOrCreateAsync(nameof(GetBaseRegion), async _ => {
|
return _cache.GetOrCreateAsync(nameof(GetBaseRegion), async _ => {
|
||||||
var rg = await ArmClient.GetResourceGroupResource(GetResourceGroupResourceIdentifier()).GetAsync();
|
var rg = await ArmClient.GetResourceGroupResource(GetResourceGroupResourceIdentifier()).GetAsync();
|
||||||
if (rg.GetRawResponse().IsError) {
|
if (rg.GetRawResponse().IsError) {
|
||||||
throw new Exception($"Failed to get base region due to [{rg.GetRawResponse().Status}] {rg.GetRawResponse().ReasonPhrase}");
|
throw new Exception($"Failed to get base region due to [{rg.GetRawResponse().Status}] {rg.GetRawResponse().ReasonPhrase}");
|
||||||
}
|
}
|
||||||
return rg.Value.Data.Location.Name;
|
return Region.Parse(rg.Value.Data.Location.Name);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -144,8 +144,8 @@ public sealed class Creds : ICreds {
|
|||||||
return resource;
|
return resource;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<IReadOnlyList<string>> GetRegions()
|
public Task<IReadOnlyList<Region>> GetRegions()
|
||||||
=> _cache.GetOrCreateAsync<IReadOnlyList<string>>(
|
=> _cache.GetOrCreateAsync<IReadOnlyList<Region>>(
|
||||||
nameof(Creds) + "." + nameof(GetRegions),
|
nameof(Creds) + "." + nameof(GetRegions),
|
||||||
async entry => {
|
async entry => {
|
||||||
// cache for one day
|
// cache for one day
|
||||||
@ -153,7 +153,7 @@ public sealed class Creds : ICreds {
|
|||||||
var subscriptionId = SubscriptionResource.CreateResourceIdentifier(GetSubscription());
|
var subscriptionId = SubscriptionResource.CreateResourceIdentifier(GetSubscription());
|
||||||
return await ArmClient.GetSubscriptionResource(subscriptionId)
|
return await ArmClient.GetSubscriptionResource(subscriptionId)
|
||||||
.GetLocationsAsync()
|
.GetLocationsAsync()
|
||||||
.Select(x => x.Name)
|
.Select(x => Region.Parse(x.Name))
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -12,15 +12,13 @@ public interface IExtensions {
|
|||||||
Async.Task<IList<VirtualMachineScaleSetExtensionData>> FuzzExtensions(Pool pool, Scaleset scaleset);
|
Async.Task<IList<VirtualMachineScaleSetExtensionData>> FuzzExtensions(Pool pool, Scaleset scaleset);
|
||||||
|
|
||||||
Async.Task<Dictionary<string, VirtualMachineExtensionData>> ReproExtensions(AzureLocation region, Os reproOs, Guid reproId, ReproConfig reproConfig, Container? setupContainer);
|
Async.Task<Dictionary<string, VirtualMachineExtensionData>> ReproExtensions(AzureLocation region, Os reproOs, Guid reproId, ReproConfig reproConfig, Container? setupContainer);
|
||||||
Task<IList<VMExtensionWrapper>> ProxyManagerExtensions(string region, Guid proxyId);
|
Task<IList<VMExtensionWrapper>> ProxyManagerExtensions(Region region, Guid proxyId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Extensions : IExtensions {
|
public class Extensions : IExtensions {
|
||||||
IOnefuzzContext _context;
|
private readonly IOnefuzzContext _context;
|
||||||
|
|
||||||
private static readonly JsonSerializerOptions _extensionSerializerOptions = new JsonSerializerOptions {
|
private static readonly JsonSerializerOptions _extensionSerializerOptions = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase };
|
||||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
|
||||||
};
|
|
||||||
|
|
||||||
public Extensions(IOnefuzzContext context) {
|
public Extensions(IOnefuzzContext context) {
|
||||||
_context = context;
|
_context = context;
|
||||||
@ -227,11 +225,12 @@ public class Extensions : IExtensions {
|
|||||||
|
|
||||||
var fileName = $"{pool.Name}/config.json";
|
var fileName = $"{pool.Name}/config.json";
|
||||||
var configJson = JsonSerializer.Serialize(config, EntityConverter.GetJsonSerializerOptions());
|
var configJson = JsonSerializer.Serialize(config, EntityConverter.GetJsonSerializerOptions());
|
||||||
await _context.Containers.SaveBlob(new Container("vm-scripts"), fileName, configJson, StorageType.Config);
|
await _context.Containers.SaveBlob(WellKnownContainers.VmScripts, fileName, configJson, StorageType.Config);
|
||||||
return await ConfigUrl(new Container("vm-scripts"), fileName, false);
|
return await ConfigUrl(WellKnownContainers.VmScripts, fileName, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public async Async.Task<Uri?> BuildScaleSetScript(Pool pool, Scaleset scaleSet) {
|
public async Async.Task<Uri?> BuildScaleSetScript(Pool pool, Scaleset scaleSet) {
|
||||||
List<string> commands = new();
|
List<string> commands = new();
|
||||||
var extension = pool.Os == Os.Windows ? "ps1" : "sh";
|
var extension = pool.Os == Os.Windows ? "ps1" : "sh";
|
||||||
@ -244,21 +243,23 @@ public class Extensions : IExtensions {
|
|||||||
commands.Add($"Set-Content -Path {sshPath} -Value \"{sshKey}\"");
|
commands.Add($"Set-Content -Path {sshPath} -Value \"{sshKey}\"");
|
||||||
}
|
}
|
||||||
|
|
||||||
await _context.Containers.SaveBlob(new Container("vm-scripts"), fileName, string.Join(sep, commands) + sep, StorageType.Config);
|
await _context.Containers.SaveBlob(WellKnownContainers.VmScripts, fileName, string.Join(sep, commands) + sep, StorageType.Config);
|
||||||
return await _context.Containers.GetFileUrl(new Container("vm-scripts"), fileName, StorageType.Config);
|
return await _context.Containers.GetFileUrl(WellKnownContainers.VmScripts, fileName, StorageType.Config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public async Async.Task UpdateManagedScripts() {
|
public async Async.Task UpdateManagedScripts() {
|
||||||
var instanceSpecificSetupSas = await _context.Containers.GetContainerSasUrl(new Container("instance-specific-setup"), StorageType.Config, BlobContainerSasPermissions.List | BlobContainerSasPermissions.Read);
|
var listAndRead = BlobContainerSasPermissions.List | BlobContainerSasPermissions.Read;
|
||||||
var toolsSas = await _context.Containers.GetContainerSasUrl(new Container("tools"), StorageType.Config, BlobContainerSasPermissions.List | BlobContainerSasPermissions.Read);
|
var instanceSpecificSetupSas = await _context.Containers.GetContainerSasUrl(WellKnownContainers.InstanceSpecificSetup, StorageType.Config, listAndRead);
|
||||||
|
var toolsSas = await _context.Containers.GetContainerSasUrl(WellKnownContainers.Tools, StorageType.Config, listAndRead);
|
||||||
|
|
||||||
string[] commands = {
|
string[] commands = {
|
||||||
$"azcopy sync '{instanceSpecificSetupSas}' instance-specific-setup",
|
$"azcopy sync '{instanceSpecificSetupSas}' instance-specific-setup",
|
||||||
$"azcopy sync '{toolsSas}' tools"
|
$"azcopy sync '{toolsSas}' tools"
|
||||||
};
|
};
|
||||||
|
|
||||||
await _context.Containers.SaveBlob(new Container("vm-scripts"), "managed.ps1", string.Join("\r\n", commands) + "\r\n", StorageType.Config);
|
await _context.Containers.SaveBlob(WellKnownContainers.VmScripts, "managed.ps1", string.Join("\r\n", commands) + "\r\n", StorageType.Config);
|
||||||
await _context.Containers.SaveBlob(new Container("vm-scripts"), "managed.sh", string.Join("\n", commands) + "\n", StorageType.Config);
|
await _context.Containers.SaveBlob(WellKnownContainers.VmScripts, "managed.sh", string.Join("\n", commands) + "\n", StorageType.Config);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Async.Task<VMExtensionWrapper> AgentConfig(AzureLocation region, Os vmOs, AgentMode mode, List<Uri>? urls = null, bool withSas = false) {
|
public async Async.Task<VMExtensionWrapper> AgentConfig(AzureLocation region, Os vmOs, AgentMode mode, List<Uri>? urls = null, bool withSas = false) {
|
||||||
@ -267,10 +268,10 @@ public class Extensions : IExtensions {
|
|||||||
|
|
||||||
var managedIdentity = JsonSerializer.Serialize(new { ManagedIdentity = new Dictionary<string, string>() }, _extensionSerializerOptions);
|
var managedIdentity = JsonSerializer.Serialize(new { ManagedIdentity = new Dictionary<string, string>() }, _extensionSerializerOptions);
|
||||||
if (vmOs == Os.Windows) {
|
if (vmOs == Os.Windows) {
|
||||||
var vmScripts = await ConfigUrl(new Container("vm-scripts"), "managed.ps1", withSas) ?? throw new Exception("failed to get VmScripts config url");
|
var vmScripts = await ConfigUrl(WellKnownContainers.VmScripts, "managed.ps1", withSas) ?? throw new Exception("failed to get VmScripts config url");
|
||||||
var toolsAzCopy = await ConfigUrl(new Container("tools"), "win64/azcopy.exe", withSas) ?? throw new Exception("failed to get toolsAzCopy config url");
|
var toolsAzCopy = await ConfigUrl(WellKnownContainers.Tools, "win64/azcopy.exe", withSas) ?? throw new Exception("failed to get toolsAzCopy config url");
|
||||||
var toolsSetup = await ConfigUrl(new Container("tools"), "win64/setup.ps1", withSas) ?? throw new Exception("failed to get toolsSetup config url");
|
var toolsSetup = await ConfigUrl(WellKnownContainers.Tools, "win64/setup.ps1", withSas) ?? throw new Exception("failed to get toolsSetup config url");
|
||||||
var toolsOneFuzz = await ConfigUrl(new Container("tools"), "win64/onefuzz.ps1", withSas) ?? throw new Exception("failed to get toolsOneFuzz config url");
|
var toolsOneFuzz = await ConfigUrl(WellKnownContainers.Tools, "win64/onefuzz.ps1", withSas) ?? throw new Exception("failed to get toolsOneFuzz config url");
|
||||||
|
|
||||||
urlsUpdated.Add(vmScripts);
|
urlsUpdated.Add(vmScripts);
|
||||||
urlsUpdated.Add(toolsAzCopy);
|
urlsUpdated.Add(toolsAzCopy);
|
||||||
@ -293,9 +294,9 @@ public class Extensions : IExtensions {
|
|||||||
return extension;
|
return extension;
|
||||||
} else if (vmOs == Os.Linux) {
|
} else if (vmOs == Os.Linux) {
|
||||||
|
|
||||||
var vmScripts = await ConfigUrl(new Container("vm-scripts"), "managed.sh", withSas) ?? throw new Exception("failed to get VmScripts config url");
|
var vmScripts = await ConfigUrl(WellKnownContainers.VmScripts, "managed.sh", withSas) ?? throw new Exception("failed to get VmScripts config url");
|
||||||
var toolsAzCopy = await ConfigUrl(new Container("tools"), "linux/azcopy", withSas) ?? throw new Exception("failed to get toolsAzCopy config url");
|
var toolsAzCopy = await ConfigUrl(WellKnownContainers.Tools, "linux/azcopy", withSas) ?? throw new Exception("failed to get toolsAzCopy config url");
|
||||||
var toolsSetup = await ConfigUrl(new Container("tools"), "linux/setup.sh", withSas) ?? throw new Exception("failed to get toolsSetup config url");
|
var toolsSetup = await ConfigUrl(WellKnownContainers.Tools, "linux/setup.sh", withSas) ?? throw new Exception("failed to get toolsSetup config url");
|
||||||
|
|
||||||
urlsUpdated.Add(vmScripts);
|
urlsUpdated.Add(vmScripts);
|
||||||
urlsUpdated.Add(toolsAzCopy);
|
urlsUpdated.Add(toolsAzCopy);
|
||||||
@ -423,7 +424,7 @@ public class Extensions : IExtensions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await _context.Containers.SaveBlob(
|
await _context.Containers.SaveBlob(
|
||||||
new Container("task-configs"),
|
WellKnownContainers.TaskConfigs,
|
||||||
$"{reproId}/{scriptName}",
|
$"{reproId}/{scriptName}",
|
||||||
taskScript,
|
taskScript,
|
||||||
StorageType.Config
|
StorageType.Config
|
||||||
@ -433,13 +434,13 @@ public class Extensions : IExtensions {
|
|||||||
urls.AddRange(new List<Uri>()
|
urls.AddRange(new List<Uri>()
|
||||||
{
|
{
|
||||||
await _context.Containers.GetFileSasUrl(
|
await _context.Containers.GetFileSasUrl(
|
||||||
new Container("repro-scripts"),
|
WellKnownContainers.ReproScripts,
|
||||||
reproFile,
|
reproFile,
|
||||||
StorageType.Config,
|
StorageType.Config,
|
||||||
BlobSasPermissions.Read
|
BlobSasPermissions.Read
|
||||||
),
|
),
|
||||||
await _context.Containers.GetFileSasUrl(
|
await _context.Containers.GetFileSasUrl(
|
||||||
new Container("task-configs"),
|
WellKnownContainers.TaskConfigs,
|
||||||
$"{reproId}/{scriptName}",
|
$"{reproId}/{scriptName}",
|
||||||
StorageType.Config,
|
StorageType.Config,
|
||||||
BlobSasPermissions.Read
|
BlobSasPermissions.Read
|
||||||
@ -460,13 +461,18 @@ public class Extensions : IExtensions {
|
|||||||
return extensionsDict;
|
return extensionsDict;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IList<VMExtensionWrapper>> ProxyManagerExtensions(string region, Guid proxyId) {
|
public async Task<IList<VMExtensionWrapper>> ProxyManagerExtensions(Region region, Guid proxyId) {
|
||||||
var config = await _context.Containers.GetFileSasUrl(new Container("proxy-configs"),
|
var config = await _context.Containers.GetFileSasUrl(
|
||||||
$"{region}/{proxyId}/config.json", StorageType.Config, BlobSasPermissions.Read);
|
WellKnownContainers.ProxyConfigs,
|
||||||
|
$"{region}/{proxyId}/config.json",
|
||||||
var proxyManager = await _context.Containers.GetFileSasUrl(new Container("tools"),
|
StorageType.Config,
|
||||||
$"linux/onefuzz-proxy-manager", StorageType.Config, BlobSasPermissions.Read);
|
BlobSasPermissions.Read);
|
||||||
|
|
||||||
|
var proxyManager = await _context.Containers.GetFileSasUrl(
|
||||||
|
WellKnownContainers.Tools,
|
||||||
|
$"linux/onefuzz-proxy-manager",
|
||||||
|
StorageType.Config,
|
||||||
|
BlobSasPermissions.Read);
|
||||||
|
|
||||||
var baseExtension =
|
var baseExtension =
|
||||||
await AgentConfig(region, Os.Linux, AgentMode.Proxy, new List<Uri> { config, proxyManager }, true);
|
await AgentConfig(region, Os.Linux, AgentMode.Proxy, new List<Uri> { config, proxyManager }, true);
|
||||||
|
@ -7,7 +7,7 @@ namespace Microsoft.OneFuzz.Service;
|
|||||||
public record ImageInfo(string Publisher, string Offer, string Sku, string Version);
|
public record ImageInfo(string Publisher, string Offer, string Sku, string Version);
|
||||||
|
|
||||||
public interface IImageOperations {
|
public interface IImageOperations {
|
||||||
public Async.Task<OneFuzzResult<Os>> GetOs(string region, string image);
|
public Async.Task<OneFuzzResult<Os>> GetOs(Region region, string image);
|
||||||
|
|
||||||
public static ImageInfo GetImageInfo(string image) {
|
public static ImageInfo GetImageInfo(string image) {
|
||||||
var imageParts = image.Split(":");
|
var imageParts = image.Split(":");
|
||||||
@ -32,7 +32,7 @@ public class ImageOperations : IImageOperations {
|
|||||||
_context = context;
|
_context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<OneFuzzResult<Os>> GetOs(string region, string image) {
|
public async Task<OneFuzzResult<Os>> GetOs(Region region, string image) {
|
||||||
string? name = null;
|
string? name = null;
|
||||||
try {
|
try {
|
||||||
var parsed = _context.Creds.ParseResourceId(image);
|
var parsed = _context.Creds.ParseResourceId(image);
|
||||||
@ -86,7 +86,7 @@ public class ImageOperations : IImageOperations {
|
|||||||
if (string.Equals(imageInfo.Version, "latest", StringComparison.Ordinal)) {
|
if (string.Equals(imageInfo.Version, "latest", StringComparison.Ordinal)) {
|
||||||
version =
|
version =
|
||||||
(await subscription.GetVirtualMachineImagesAsync(
|
(await subscription.GetVirtualMachineImagesAsync(
|
||||||
region,
|
region.String,
|
||||||
imageInfo.Publisher,
|
imageInfo.Publisher,
|
||||||
imageInfo.Offer,
|
imageInfo.Offer,
|
||||||
imageInfo.Sku,
|
imageInfo.Sku,
|
||||||
@ -97,7 +97,7 @@ public class ImageOperations : IImageOperations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
name = (await subscription.GetVirtualMachineImageAsync(
|
name = (await subscription.GetVirtualMachineImageAsync(
|
||||||
region,
|
region.String,
|
||||||
imageInfo.Publisher,
|
imageInfo.Publisher,
|
||||||
imageInfo.Offer,
|
imageInfo.Offer,
|
||||||
imageInfo.Sku
|
imageInfo.Sku
|
||||||
|
@ -12,7 +12,7 @@ namespace Microsoft.OneFuzz.Service;
|
|||||||
public interface IIpOperations {
|
public interface IIpOperations {
|
||||||
public Async.Task<NetworkInterfaceResource?> GetPublicNic(string resourceGroup, string name);
|
public Async.Task<NetworkInterfaceResource?> GetPublicNic(string resourceGroup, string name);
|
||||||
|
|
||||||
public Async.Task<OneFuzzResultVoid> CreatePublicNic(string resourceGroup, string name, string region, Nsg? nsg);
|
public Async.Task<OneFuzzResultVoid> CreatePublicNic(string resourceGroup, string name, Region region, Nsg? nsg);
|
||||||
|
|
||||||
public Async.Task<string?> GetPublicIp(ResourceIdentifier resourceId);
|
public Async.Task<string?> GetPublicIp(ResourceIdentifier resourceId);
|
||||||
|
|
||||||
@ -26,7 +26,7 @@ public interface IIpOperations {
|
|||||||
|
|
||||||
public Async.Task<string?> GetScalesetInstanceIp(Guid scalesetId, Guid machineId);
|
public Async.Task<string?> GetScalesetInstanceIp(Guid scalesetId, Guid machineId);
|
||||||
|
|
||||||
public Async.Task CreateIp(string resourceGroup, string name, string region);
|
public Async.Task CreateIp(string resourceGroup, string name, Region region);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -120,7 +120,7 @@ public class IpOperations : IIpOperations {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<OneFuzzResultVoid> CreatePublicNic(string resourceGroup, string name, string region, Nsg? nsg) {
|
public async Task<OneFuzzResultVoid> CreatePublicNic(string resourceGroup, string name, Region region, Nsg? nsg) {
|
||||||
_logTracer.Info($"creating nic for {resourceGroup}:{name} in {region}");
|
_logTracer.Info($"creating nic for {resourceGroup}:{name} in {region}");
|
||||||
|
|
||||||
var network = await Network.Init(region, _context);
|
var network = await Network.Init(region, _context);
|
||||||
@ -190,7 +190,7 @@ public class IpOperations : IIpOperations {
|
|||||||
return OneFuzzResultVoid.Ok;
|
return OneFuzzResultVoid.Ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Async.Task CreateIp(string resourceGroup, string name, string region) {
|
public async Async.Task CreateIp(string resourceGroup, string name, Region region) {
|
||||||
var ipParams = new PublicIPAddressData() {
|
var ipParams = new PublicIPAddressData() {
|
||||||
Location = region,
|
Location = region,
|
||||||
PublicIPAllocationMethod = NetworkIPAllocationMethod.Dynamic
|
PublicIPAllocationMethod = NetworkIPAllocationMethod.Dynamic
|
||||||
@ -259,5 +259,3 @@ public class IpOperations : IIpOperations {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ namespace Microsoft.OneFuzz.Service;
|
|||||||
public class Network {
|
public class Network {
|
||||||
private readonly string _name;
|
private readonly string _name;
|
||||||
private readonly string _group;
|
private readonly string _group;
|
||||||
private readonly string _region;
|
private readonly Region _region;
|
||||||
private readonly IOnefuzzContext _context;
|
private readonly IOnefuzzContext _context;
|
||||||
|
|
||||||
private readonly NetworkConfig _networkConfig;
|
private readonly NetworkConfig _networkConfig;
|
||||||
@ -14,7 +14,7 @@ public class Network {
|
|||||||
// This was generated randomly and should be preserved moving forwards
|
// This was generated randomly and should be preserved moving forwards
|
||||||
static Guid NETWORK_GUID_NAMESPACE = Guid.Parse("372977ad-b533-416a-b1b4-f770898e0b11");
|
static Guid NETWORK_GUID_NAMESPACE = Guid.Parse("372977ad-b533-416a-b1b4-f770898e0b11");
|
||||||
|
|
||||||
public Network(string region, string group, string name, IOnefuzzContext context, NetworkConfig networkConfig) {
|
public Network(Region region, string group, string name, IOnefuzzContext context, NetworkConfig networkConfig) {
|
||||||
_region = region;
|
_region = region;
|
||||||
_group = group;
|
_group = group;
|
||||||
_name = name;
|
_name = name;
|
||||||
@ -22,7 +22,7 @@ public class Network {
|
|||||||
_networkConfig = networkConfig;
|
_networkConfig = networkConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Async.Task<Network> Init(string region, IOnefuzzContext context) {
|
public static async Async.Task<Network> Init(Region region, IOnefuzzContext context) {
|
||||||
var group = context.Creds.GetBaseResourceGroup();
|
var group = context.Creds.GetBaseResourceGroup();
|
||||||
var instanceConfig = await context.ConfigOperations.Fetch();
|
var instanceConfig = await context.ConfigOperations.Fetch();
|
||||||
var networkConfig = instanceConfig.NetworkConfig;
|
var networkConfig = instanceConfig.NetworkConfig;
|
||||||
@ -33,16 +33,14 @@ public class Network {
|
|||||||
// configs.
|
// configs.
|
||||||
|
|
||||||
string name;
|
string name;
|
||||||
|
|
||||||
if (networkConfig.AddressSpace == NetworkConfig.Default.AddressSpace && networkConfig.Subnet == NetworkConfig.Default.Subnet) {
|
if (networkConfig.AddressSpace == NetworkConfig.Default.AddressSpace && networkConfig.Subnet == NetworkConfig.Default.Subnet) {
|
||||||
name = region;
|
name = region.String;
|
||||||
} else {
|
} else {
|
||||||
//TODO: Remove dependency on "Faithlife"
|
//TODO: Remove dependency on "Faithlife"
|
||||||
var networkId = Faithlife.Utility.GuidUtility.Create(NETWORK_GUID_NAMESPACE, string.Join("|", networkConfig.AddressSpace, networkConfig.Subnet), 5);
|
var networkId = Faithlife.Utility.GuidUtility.Create(NETWORK_GUID_NAMESPACE, string.Join("|", networkConfig.AddressSpace, networkConfig.Subnet), 5);
|
||||||
name = $"{region}-{networkId}";
|
name = $"{region}-{networkId}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return new Network(region, group, name, context, networkConfig);
|
return new Network(region, group, name, context, networkConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ namespace Microsoft.OneFuzz.Service;
|
|||||||
public interface INotificationOperations : IOrm<Notification> {
|
public interface INotificationOperations : IOrm<Notification> {
|
||||||
Async.Task NewFiles(Container container, string filename, bool failTaskOnTransientError);
|
Async.Task NewFiles(Container container, string filename, bool failTaskOnTransientError);
|
||||||
IAsyncEnumerable<Notification> GetNotifications(Container container);
|
IAsyncEnumerable<Notification> GetNotifications(Container container);
|
||||||
IAsyncEnumerable<(Task, IEnumerable<string>)> GetQueueTasks();
|
IAsyncEnumerable<(Task, IEnumerable<Container>)> GetQueueTasks();
|
||||||
Async.Task<OneFuzzResult<Notification>> Create(Container container, NotificationTemplate config, bool replaceExisting);
|
Async.Task<OneFuzzResult<Notification>> Create(Container container, NotificationTemplate config, bool replaceExisting);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,8 +52,8 @@ public class NotificationOperations : Orm<Notification>, INotificationOperations
|
|||||||
}
|
}
|
||||||
|
|
||||||
await foreach (var (task, containers) in GetQueueTasks()) {
|
await foreach (var (task, containers) in GetQueueTasks()) {
|
||||||
if (containers.Contains(container.ContainerName)) {
|
if (containers.Contains(container)) {
|
||||||
_logTracer.Info($"queuing input {container.ContainerName} {filename} {task.TaskId}");
|
_logTracer.Info($"queuing input {container} {filename} {task.TaskId}");
|
||||||
var url = _context.Containers.GetFileSasUrl(container, filename, StorageType.Corpus, BlobSasPermissions.Read | BlobSasPermissions.Delete);
|
var url = _context.Containers.GetFileSasUrl(container, filename, StorageType.Corpus, BlobSasPermissions.Read | BlobSasPermissions.Delete);
|
||||||
await _context.Queue.SendMessage(task.TaskId.ToString(), url?.ToString() ?? "", StorageType.Corpus);
|
await _context.Queue.SendMessage(task.TaskId.ToString(), url?.ToString() ?? "", StorageType.Corpus);
|
||||||
}
|
}
|
||||||
@ -77,10 +77,10 @@ public class NotificationOperations : Orm<Notification>, INotificationOperations
|
|||||||
}
|
}
|
||||||
|
|
||||||
public IAsyncEnumerable<Notification> GetNotifications(Container container) {
|
public IAsyncEnumerable<Notification> GetNotifications(Container container) {
|
||||||
return QueryAsync(filter: $"container eq '{container.ContainerName}'");
|
return QueryAsync(filter: $"container eq '{container}'");
|
||||||
}
|
}
|
||||||
|
|
||||||
public IAsyncEnumerable<(Task, IEnumerable<string>)> GetQueueTasks() {
|
public IAsyncEnumerable<(Task, IEnumerable<Container>)> GetQueueTasks() {
|
||||||
// Nullability mismatch: We filter tuples where the containers are null
|
// Nullability mismatch: We filter tuples where the containers are null
|
||||||
return _context.TaskOperations.SearchStates(states: TaskStateHelper.AvailableStates)
|
return _context.TaskOperations.SearchStates(states: TaskStateHelper.AvailableStates)
|
||||||
.Select(task => (task, _context.TaskOperations.GetInputContainerQueues(task.Config)))
|
.Select(task => (task, _context.TaskOperations.GetInputContainerQueues(task.Config)))
|
||||||
@ -93,7 +93,7 @@ public class NotificationOperations : Orm<Notification>, INotificationOperations
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (replaceExisting) {
|
if (replaceExisting) {
|
||||||
var existing = this.SearchByRowKeys(new[] { container.ContainerName });
|
var existing = this.SearchByRowKeys(new[] { container.String });
|
||||||
await foreach (var existingEntry in existing) {
|
await foreach (var existingEntry in existing) {
|
||||||
_logTracer.Info($"replacing existing notification: {existingEntry.NotificationId} - {container}");
|
_logTracer.Info($"replacing existing notification: {existingEntry.NotificationId} - {container}");
|
||||||
await this.Delete(existingEntry);
|
await this.Delete(existingEntry);
|
||||||
|
@ -8,7 +8,7 @@ namespace Microsoft.OneFuzz.Service {
|
|||||||
Async.Task<NetworkSecurityGroupResource?> GetNsg(string name);
|
Async.Task<NetworkSecurityGroupResource?> GetNsg(string name);
|
||||||
public Async.Task<OneFuzzResult<bool>> AssociateSubnet(string name, VirtualNetworkResource vnet, SubnetResource subnet);
|
public Async.Task<OneFuzzResult<bool>> AssociateSubnet(string name, VirtualNetworkResource vnet, SubnetResource subnet);
|
||||||
IAsyncEnumerable<NetworkSecurityGroupResource> ListNsgs();
|
IAsyncEnumerable<NetworkSecurityGroupResource> ListNsgs();
|
||||||
bool OkToDelete(HashSet<string> active_regions, string nsg_region, string nsg_name);
|
bool OkToDelete(IReadOnlySet<Region> active_regions, Region nsg_region, string nsg_name);
|
||||||
Async.Task<bool> StartDeleteNsg(string name);
|
Async.Task<bool> StartDeleteNsg(string name);
|
||||||
|
|
||||||
Async.Task<OneFuzzResultVoid> DissociateNic(Nsg nsg, NetworkInterfaceResource nic);
|
Async.Task<OneFuzzResultVoid> DissociateNic(Nsg nsg, NetworkInterfaceResource nic);
|
||||||
@ -128,8 +128,8 @@ namespace Microsoft.OneFuzz.Service {
|
|||||||
return _context.Creds.GetResourceGroupResource().GetNetworkSecurityGroups().GetAllAsync();
|
return _context.Creds.GetResourceGroupResource().GetNetworkSecurityGroups().GetAllAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool OkToDelete(HashSet<string> active_regions, string nsg_region, string nsg_name) {
|
public bool OkToDelete(IReadOnlySet<Region> active_regions, Region nsg_region, string nsg_name) {
|
||||||
return !active_regions.Contains(nsg_region) && nsg_region == nsg_name;
|
return !active_regions.Contains(nsg_region) && Nsg.NameFromRegion(nsg_region) == nsg_name;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -164,7 +164,7 @@ namespace Microsoft.OneFuzz.Service {
|
|||||||
return await CreateNsg(nsg.Name, nsg.Region);
|
return await CreateNsg(nsg.Name, nsg.Region);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<OneFuzzResultVoid> CreateNsg(string name, string location) {
|
private async Task<OneFuzzResultVoid> CreateNsg(string name, Region location) {
|
||||||
var resourceGroup = _context.Creds.GetBaseResourceGroup();
|
var resourceGroup = _context.Creds.GetBaseResourceGroup();
|
||||||
_logTracer.Info($"creating nsg {resourceGroup}:{location}:{name}");
|
_logTracer.Info($"creating nsg {resourceGroup}:{location}:{name}");
|
||||||
|
|
||||||
|
@ -5,10 +5,10 @@ namespace Microsoft.OneFuzz.Service;
|
|||||||
|
|
||||||
|
|
||||||
public interface IProxyForwardOperations : IOrm<ProxyForward> {
|
public interface IProxyForwardOperations : IOrm<ProxyForward> {
|
||||||
IAsyncEnumerable<ProxyForward> SearchForward(Guid? scalesetId = null, string? region = null, Guid? machineId = null, Guid? proxyId = null, int? dstPort = null);
|
IAsyncEnumerable<ProxyForward> SearchForward(Guid? 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(string region, Guid scalesetId, Guid machineId, int dstPort, int duration);
|
Task<OneFuzzResult<ProxyForward>> UpdateOrCreate(Region region, Guid scalesetId, Guid machineId, int dstPort, int duration);
|
||||||
Task<HashSet<string>> RemoveForward(Guid scalesetId, Guid? machineId = null, int? dstPort = null, Guid? proxyId = null);
|
Task<HashSet<Region>> RemoveForward(Guid scalesetId, Guid? machineId = null, int? dstPort = null, Guid? proxyId = null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -20,7 +20,7 @@ public class ProxyForwardOperations : Orm<ProxyForward>, IProxyForwardOperations
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public IAsyncEnumerable<ProxyForward> SearchForward(Guid? scalesetId = null, string? region = null, Guid? machineId = null, Guid? proxyId = null, int? dstPort = null) {
|
public IAsyncEnumerable<ProxyForward> SearchForward(Guid? scalesetId = null, Region? region = null, Guid? machineId = null, Guid? proxyId = null, int? dstPort = null) {
|
||||||
|
|
||||||
var conditions =
|
var conditions =
|
||||||
new[] {
|
new[] {
|
||||||
@ -40,7 +40,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(string region, Guid scalesetId, Guid machineId, int dstPort, int duration) {
|
public async Task<OneFuzzResult<ProxyForward>> UpdateOrCreate(Region region, Guid 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) {
|
||||||
@ -88,10 +88,10 @@ public class ProxyForwardOperations : Orm<ProxyForward>, IProxyForwardOperations
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<HashSet<string>> RemoveForward(Guid scalesetId, Guid? machineId, int? dstPort, Guid? proxyId) {
|
public async Task<HashSet<Region>> RemoveForward(Guid 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<string>();
|
var regions = new HashSet<Region>();
|
||||||
foreach (var entry in entries) {
|
foreach (var entry in entries) {
|
||||||
regions.Add(entry.Region);
|
regions.Add(entry.Region);
|
||||||
await Delete(entry);
|
await Delete(entry);
|
||||||
|
@ -14,7 +14,7 @@ public interface IProxyOperations : IStatefulOrm<Proxy, VmState> {
|
|||||||
bool IsAlive(Proxy proxy);
|
bool IsAlive(Proxy proxy);
|
||||||
Async.Task SaveProxyConfig(Proxy proxy);
|
Async.Task SaveProxyConfig(Proxy proxy);
|
||||||
bool IsOutdated(Proxy proxy);
|
bool IsOutdated(Proxy proxy);
|
||||||
Async.Task<Proxy?> GetOrCreate(string region);
|
Async.Task<Proxy?> GetOrCreate(Region region);
|
||||||
Task<bool> IsUsed(Proxy proxy);
|
Task<bool> IsUsed(Proxy proxy);
|
||||||
|
|
||||||
// state transitions:
|
// state transitions:
|
||||||
@ -27,13 +27,10 @@ public interface IProxyOperations : IStatefulOrm<Proxy, VmState> {
|
|||||||
Async.Task<Proxy> Stopped(Proxy proxy);
|
Async.Task<Proxy> Stopped(Proxy proxy);
|
||||||
}
|
}
|
||||||
public class ProxyOperations : StatefulOrm<Proxy, VmState, ProxyOperations>, IProxyOperations {
|
public class ProxyOperations : StatefulOrm<Proxy, VmState, ProxyOperations>, IProxyOperations {
|
||||||
|
static readonly TimeSpan PROXY_LIFESPAN = TimeSpan.FromDays(7);
|
||||||
|
|
||||||
static TimeSpan PROXY_LIFESPAN = TimeSpan.FromDays(7);
|
|
||||||
|
|
||||||
public ProxyOperations(ILogTracer log, IOnefuzzContext context)
|
public ProxyOperations(ILogTracer log, IOnefuzzContext context)
|
||||||
: base(log.WithTag("Component", "scaleset-proxy"), context) {
|
: base(log.WithTag("Component", "scaleset-proxy"), context) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -44,7 +41,7 @@ public class ProxyOperations : StatefulOrm<Proxy, VmState, ProxyOperations>, IPr
|
|||||||
return await data.FirstOrDefaultAsync();
|
return await data.FirstOrDefaultAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Async.Task<Proxy?> GetOrCreate(string region) {
|
public async Async.Task<Proxy?> GetOrCreate(Region region) {
|
||||||
var proxyList = QueryAsync(filter: $"region eq '{region}' and outdated eq false");
|
var proxyList = QueryAsync(filter: $"region eq '{region}' and outdated eq false");
|
||||||
|
|
||||||
await foreach (var proxy in proxyList) {
|
await foreach (var proxy in proxyList) {
|
||||||
@ -117,7 +114,7 @@ public class ProxyOperations : StatefulOrm<Proxy, VmState, ProxyOperations>, IPr
|
|||||||
|
|
||||||
public async Async.Task SaveProxyConfig(Proxy proxy) {
|
public async Async.Task SaveProxyConfig(Proxy proxy) {
|
||||||
var forwards = await GetForwards(proxy);
|
var forwards = await GetForwards(proxy);
|
||||||
var url = (await _context.Containers.GetFileSasUrl(new Container("proxy-configs"), $"{proxy.Region}/{proxy.ProxyId}/config.json", StorageType.Config, BlobSasPermissions.Read)).EnsureNotNull("Can't generate file sas");
|
var url = (await _context.Containers.GetFileSasUrl(WellKnownContainers.ProxyConfigs, $"{proxy.Region}/{proxy.ProxyId}/config.json", StorageType.Config, BlobSasPermissions.Read)).EnsureNotNull("Can't generate file sas");
|
||||||
var queueSas = await _context.Queue.GetQueueSas("proxy", StorageType.Config, QueueSasPermissions.Add).EnsureNotNull("can't generate queue sas") ?? throw new Exception("Queue sas is null");
|
var queueSas = await _context.Queue.GetQueueSas("proxy", StorageType.Config, QueueSasPermissions.Add).EnsureNotNull("can't generate queue sas") ?? throw new Exception("Queue sas is null");
|
||||||
|
|
||||||
var proxyConfig = new ProxyConfig(
|
var proxyConfig = new ProxyConfig(
|
||||||
@ -130,7 +127,7 @@ public class ProxyOperations : StatefulOrm<Proxy, VmState, ProxyOperations>, IPr
|
|||||||
MicrosoftTelemetryKey: _context.ServiceConfiguration.OneFuzzTelemetry.EnsureNotNull("missing Telemetry"),
|
MicrosoftTelemetryKey: _context.ServiceConfiguration.OneFuzzTelemetry.EnsureNotNull("missing Telemetry"),
|
||||||
InstanceId: await _context.Containers.GetInstanceId());
|
InstanceId: await _context.Containers.GetInstanceId());
|
||||||
|
|
||||||
await _context.Containers.SaveBlob(new Container("proxy-configs"), $"{proxy.Region}/{proxy.ProxyId}/config.json", EntityConverter.ToJsonString(proxyConfig), StorageType.Config);
|
await _context.Containers.SaveBlob(WellKnownContainers.ProxyConfigs, $"{proxy.Region}/{proxy.ProxyId}/config.json", EntityConverter.ToJsonString(proxyConfig), StorageType.Config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -175,7 +172,7 @@ public class ProxyOperations : StatefulOrm<Proxy, VmState, ProxyOperations>, IPr
|
|||||||
return await SetState(proxy, VmState.ExtensionsLaunch);
|
return await SetState(proxy, VmState.ExtensionsLaunch);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
var nsg = new Nsg(proxy.Region, proxy.Region);
|
var nsg = Nsg.ForRegion(proxy.Region);
|
||||||
var result = await _context.NsgOperations.Create(nsg);
|
var result = await _context.NsgOperations.Create(nsg);
|
||||||
if (!result.IsOk) {
|
if (!result.IsOk) {
|
||||||
return await SetFailed(proxy, result.ErrorV);
|
return await SetFailed(proxy, result.ErrorV);
|
||||||
|
@ -27,7 +27,7 @@ public class Reports : IReports {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async Async.Task<IReport?> GetReportOrRegression(Container container, string fileName, bool expectReports = false, params string[] args) {
|
public async Async.Task<IReport?> GetReportOrRegression(Container container, string fileName, bool expectReports = false, params string[] args) {
|
||||||
var filePath = String.Join("/", new[] { container.ContainerName, fileName });
|
var filePath = string.Join("/", new[] { container.String, fileName });
|
||||||
if (!fileName.EndsWith(".json", StringComparison.Ordinal)) {
|
if (!fileName.EndsWith(".json", StringComparison.Ordinal)) {
|
||||||
if (expectReports) {
|
if (expectReports) {
|
||||||
_log.Error($"get_report invalid extension: {filePath}");
|
_log.Error($"get_report invalid extension: {filePath}");
|
||||||
|
@ -131,7 +131,7 @@ public class ReproOperations : StatefulOrm<Repro, VmState, ReproOperations>, IRe
|
|||||||
repro = repro with { State = VmState.ExtensionsLaunch };
|
repro = repro with { State = VmState.ExtensionsLaunch };
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
var nsg = new Nsg(vm.Region, vm.Region);
|
var nsg = Nsg.ForRegion(vm.Region);
|
||||||
var result = await _context.NsgOperations.Create(nsg);
|
var result = await _context.NsgOperations.Create(nsg);
|
||||||
if (!result.IsOk) {
|
if (!result.IsOk) {
|
||||||
return await _context.ReproOperations.SetError(repro, result.ErrorV);
|
return await _context.ReproOperations.SetError(repro, result.ErrorV);
|
||||||
@ -260,7 +260,7 @@ public class ReproOperations : StatefulOrm<Repro, VmState, ReproOperations>, IRe
|
|||||||
|
|
||||||
foreach (var (fileName, fileContents) in files) {
|
foreach (var (fileName, fileContents) in files) {
|
||||||
await _context.Containers.SaveBlob(
|
await _context.Containers.SaveBlob(
|
||||||
new Container("repro-scripts"),
|
WellKnownContainers.ReproScripts,
|
||||||
$"{repro.VmId}/{fileName}",
|
$"{repro.VmId}/{fileName}",
|
||||||
fileContents,
|
fileContents,
|
||||||
StorageType.Config
|
StorageType.Config
|
||||||
|
@ -184,7 +184,7 @@ public class Scheduler : IScheduler {
|
|||||||
return (bucketConfig, workUnit);
|
return (bucketConfig, workUnit);
|
||||||
}
|
}
|
||||||
|
|
||||||
public record struct BucketId(Os os, Guid jobId, (string, string)? vm, PoolName? pool, string setupContainer, bool? reboot, Guid? unique);
|
public record struct BucketId(Os os, Guid jobId, (string, string)? vm, PoolName? pool, Container setupContainer, bool? reboot, Guid? unique);
|
||||||
|
|
||||||
public static ILookup<BucketId, Task> BucketTasks(IEnumerable<Task> tasks) {
|
public static ILookup<BucketId, Task> BucketTasks(IEnumerable<Task> tasks) {
|
||||||
|
|
||||||
@ -221,11 +221,11 @@ public class Scheduler : IScheduler {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static string GetSetupContainer(TaskConfig config) {
|
static Container GetSetupContainer(TaskConfig config) {
|
||||||
|
|
||||||
foreach (var container in config.Containers ?? throw new Exception("Missing containers")) {
|
foreach (var container in config.Containers ?? throw new Exception("Missing containers")) {
|
||||||
if (container.Type == ContainerType.Setup) {
|
if (container.Type == ContainerType.Setup) {
|
||||||
return container.Name.ContainerName;
|
return container.Name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ public interface ISubnet {
|
|||||||
|
|
||||||
Async.Task<SubnetResource?> GetSubnet(string vnetName, string subnetName);
|
Async.Task<SubnetResource?> GetSubnet(string vnetName, string subnetName);
|
||||||
|
|
||||||
Async.Task<OneFuzzResultVoid> CreateVirtualNetwork(string resourceGroup, string name, string region, NetworkConfig networkConfig);
|
Async.Task<OneFuzzResultVoid> CreateVirtualNetwork(string resourceGroup, string name, Region region, NetworkConfig networkConfig);
|
||||||
|
|
||||||
Async.Task<ResourceIdentifier?> GetSubnetId(string name, string subnetName);
|
Async.Task<ResourceIdentifier?> GetSubnetId(string name, string subnetName);
|
||||||
}
|
}
|
||||||
@ -29,7 +29,7 @@ public class Subnet : ISubnet {
|
|||||||
_context = context;
|
_context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<OneFuzzResultVoid> CreateVirtualNetwork(string resourceGroup, string name, string region, NetworkConfig networkConfig) {
|
public async Task<OneFuzzResultVoid> CreateVirtualNetwork(string resourceGroup, string name, Region region, NetworkConfig networkConfig) {
|
||||||
_logTracer.Info($"creating subnet - resource group:{resourceGroup} name:{name} region: {region}");
|
_logTracer.Info($"creating subnet - resource group:{resourceGroup} name:{name} region: {region}");
|
||||||
|
|
||||||
var virtualNetParam = new VirtualNetworkData {
|
var virtualNetParam = new VirtualNetworkData {
|
||||||
|
@ -15,7 +15,7 @@ public interface ITaskOperations : IStatefulOrm<Task, TaskState> {
|
|||||||
|
|
||||||
IAsyncEnumerable<Task> SearchStates(Guid? jobId = null, IEnumerable<TaskState>? states = null);
|
IAsyncEnumerable<Task> SearchStates(Guid? jobId = null, IEnumerable<TaskState>? states = null);
|
||||||
|
|
||||||
IEnumerable<string>? GetInputContainerQueues(TaskConfig config);
|
IEnumerable<Container>? GetInputContainerQueues(TaskConfig config);
|
||||||
|
|
||||||
IAsyncEnumerable<Task> SearchExpired();
|
IAsyncEnumerable<Task> SearchExpired();
|
||||||
Async.Task MarkStopping(Task task);
|
Async.Task MarkStopping(Task task);
|
||||||
@ -75,7 +75,7 @@ public class TaskOperations : StatefulOrm<Task, TaskState, TaskOperations>, ITas
|
|||||||
return QueryAsync(filter: queryString);
|
return QueryAsync(filter: queryString);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<string>? GetInputContainerQueues(TaskConfig config) {
|
public IEnumerable<Container>? GetInputContainerQueues(TaskConfig config) {
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -226,7 +226,7 @@ public class VmOperations : IVmOperations {
|
|||||||
|
|
||||||
async Task<OneFuzzResultVoid> CreateVm(
|
async Task<OneFuzzResultVoid> CreateVm(
|
||||||
string name,
|
string name,
|
||||||
string location,
|
Region location,
|
||||||
string vmSku,
|
string vmSku,
|
||||||
string image,
|
string image,
|
||||||
string password,
|
string password,
|
||||||
|
@ -16,7 +16,7 @@ public interface IVmssOperations {
|
|||||||
Async.Task<OneFuzzResultVoid> UpdateExtensions(Guid name, IList<VirtualMachineScaleSetExtensionData> extensions);
|
Async.Task<OneFuzzResultVoid> UpdateExtensions(Guid name, IList<VirtualMachineScaleSetExtensionData> extensions);
|
||||||
Async.Task<VirtualMachineScaleSetData?> GetVmss(Guid name);
|
Async.Task<VirtualMachineScaleSetData?> GetVmss(Guid name);
|
||||||
|
|
||||||
Async.Task<IReadOnlyList<string>> ListAvailableSkus(string region);
|
Async.Task<IReadOnlyList<string>> ListAvailableSkus(Region region);
|
||||||
|
|
||||||
Async.Task<bool> DeleteVmss(Guid name, bool? forceDeletion = null);
|
Async.Task<bool> DeleteVmss(Guid name, bool? forceDeletion = null);
|
||||||
|
|
||||||
@ -27,7 +27,7 @@ public interface IVmssOperations {
|
|||||||
Async.Task<OneFuzzResultVoid> ResizeVmss(Guid name, long capacity);
|
Async.Task<OneFuzzResultVoid> ResizeVmss(Guid name, long capacity);
|
||||||
|
|
||||||
Async.Task<OneFuzzResultVoid> CreateVmss(
|
Async.Task<OneFuzzResultVoid> CreateVmss(
|
||||||
string location,
|
Region location,
|
||||||
Guid name,
|
Guid name,
|
||||||
string vmSku,
|
string vmSku,
|
||||||
long vmCount,
|
long vmCount,
|
||||||
@ -236,7 +236,7 @@ public class VmssOperations : IVmssOperations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async Async.Task<OneFuzzResultVoid> CreateVmss(
|
public async Async.Task<OneFuzzResultVoid> CreateVmss(
|
||||||
string location,
|
Region location,
|
||||||
Guid name,
|
Guid name,
|
||||||
string vmSku,
|
string vmSku,
|
||||||
long vmCount,
|
long vmCount,
|
||||||
@ -394,7 +394,7 @@ public class VmssOperations : IVmssOperations {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Async.Task<IReadOnlyList<string>> ListAvailableSkus(string region)
|
public Async.Task<IReadOnlyList<string>> ListAvailableSkus(Region region)
|
||||||
=> _cache.GetOrCreateAsync<IReadOnlyList<string>>($"compute-skus-{region}", async entry => {
|
=> _cache.GetOrCreateAsync<IReadOnlyList<string>>($"compute-skus-{region}", async entry => {
|
||||||
entry.SetAbsoluteExpiration(TimeSpan.FromMinutes(10));
|
entry.SetAbsoluteExpiration(TimeSpan.FromMinutes(10));
|
||||||
|
|
||||||
@ -407,7 +407,7 @@ public class VmssOperations : IVmssOperations {
|
|||||||
if (sku.Restrictions is not null) {
|
if (sku.Restrictions is not null) {
|
||||||
foreach (var restriction in sku.Restrictions) {
|
foreach (var restriction in sku.Restrictions) {
|
||||||
if (restriction.RestrictionsType == ResourceSkuRestrictionsType.Location &&
|
if (restriction.RestrictionsType == ResourceSkuRestrictionsType.Location &&
|
||||||
restriction.Values.Contains(region, StringComparer.OrdinalIgnoreCase)) {
|
restriction.Values.Contains(region.String, StringComparer.OrdinalIgnoreCase)) {
|
||||||
available = false;
|
available = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
12
src/ApiService/ApiService/onefuzzlib/WellKnownContainers.cs
Normal file
12
src/ApiService/ApiService/onefuzzlib/WellKnownContainers.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
namespace Microsoft.OneFuzz.Service;
|
||||||
|
|
||||||
|
public static class WellKnownContainers {
|
||||||
|
public static readonly Container BaseConfig = Container.Parse("base-config");
|
||||||
|
public static readonly Container VmScripts = Container.Parse("vm-scripts");
|
||||||
|
public static readonly Container InstanceSpecificSetup = Container.Parse("instance-specific-setup");
|
||||||
|
public static readonly Container Tools = Container.Parse("tools");
|
||||||
|
public static readonly Container ReproScripts = Container.Parse("repro-scripts");
|
||||||
|
public static readonly Container TaskConfigs = Container.Parse("task-configs");
|
||||||
|
public static readonly Container ProxyConfigs = Container.Parse("proxy-configs");
|
||||||
|
}
|
@ -225,17 +225,23 @@ public class EntityConverter {
|
|||||||
private object? GetFieldValue(EntityInfo info, string name, TableEntity entity) {
|
private object? GetFieldValue(EntityInfo info, string name, TableEntity entity) {
|
||||||
var ef = info.properties[name].First();
|
var ef = info.properties[name].First();
|
||||||
if (ef.kind == EntityPropertyKind.PartitionKey || ef.kind == EntityPropertyKind.RowKey) {
|
if (ef.kind == EntityPropertyKind.PartitionKey || ef.kind == EntityPropertyKind.RowKey) {
|
||||||
if (ef.type == typeof(string))
|
// partition & row keys must always be strings
|
||||||
return entity.GetString(ef.kind.ToString());
|
var stringValue = entity.GetString(ef.kind.ToString());
|
||||||
else if (ef.type == typeof(Guid))
|
if (ef.type == typeof(string)) {
|
||||||
return Guid.Parse(entity.GetString(ef.kind.ToString()));
|
return stringValue;
|
||||||
else if (ef.type == typeof(int))
|
} else if (ef.type == typeof(Guid)) {
|
||||||
return int.Parse(entity.GetString(ef.kind.ToString()));
|
return Guid.Parse(stringValue);
|
||||||
else if (ef.type == typeof(long))
|
} else if (ef.type == typeof(int)) {
|
||||||
return long.Parse(entity.GetString(ef.kind.ToString()));
|
return int.Parse(stringValue);
|
||||||
else if (ef.type.IsClass)
|
} else if (ef.type == typeof(long)) {
|
||||||
return ef.type.GetConstructor(new[] { typeof(string) })!.Invoke(new[] { entity.GetString(ef.kind.ToString()) });
|
return long.Parse(stringValue);
|
||||||
else {
|
} else if (ef.type.IsClass) {
|
||||||
|
if (ef.type.IsAssignableTo(typeof(ValidatedString))) {
|
||||||
|
return ef.type.GetMethod("Parse")!.Invoke(null, new[] { stringValue });
|
||||||
|
}
|
||||||
|
|
||||||
|
return Activator.CreateInstance(ef.type, new[] { stringValue });
|
||||||
|
} else {
|
||||||
throw new Exception($"invalid partition or row key type of {info.type} property {name}: {ef.type}");
|
throw new Exception($"invalid partition or row key type of {info.type} property {name}: {ef.type}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,11 +48,11 @@ public abstract class ContainersTestBase : FunctionTestBase {
|
|||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Async.Task CanDelete() {
|
public async Async.Task CanDelete() {
|
||||||
var containerName = "test";
|
var containerName = Container.Parse("test");
|
||||||
var client = GetContainerClient(containerName);
|
var client = GetContainerClient(containerName);
|
||||||
await client.CreateIfNotExistsAsync();
|
await client.CreateIfNotExistsAsync();
|
||||||
|
|
||||||
var msg = TestHttpRequestData.FromJson("DELETE", new ContainerDelete(new Container(containerName)));
|
var msg = TestHttpRequestData.FromJson("DELETE", new ContainerDelete(containerName));
|
||||||
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
||||||
var func = new ContainersFunction(Logger, auth, Context);
|
var func = new ContainersFunction(Logger, auth, Context);
|
||||||
@ -67,8 +67,8 @@ public abstract class ContainersTestBase : FunctionTestBase {
|
|||||||
[Fact]
|
[Fact]
|
||||||
public async Async.Task CanPost_New() {
|
public async Async.Task CanPost_New() {
|
||||||
var meta = new Dictionary<string, string> { { "some", "value" } };
|
var meta = new Dictionary<string, string> { { "some", "value" } };
|
||||||
var containerName = "test";
|
var containerName = Container.Parse("test");
|
||||||
var msg = TestHttpRequestData.FromJson("POST", new ContainerCreate(new Container(containerName), meta));
|
var msg = TestHttpRequestData.FromJson("POST", new ContainerCreate(containerName, meta));
|
||||||
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
||||||
var func = new ContainersFunction(Logger, auth, Context);
|
var func = new ContainersFunction(Logger, auth, Context);
|
||||||
@ -87,12 +87,12 @@ public abstract class ContainersTestBase : FunctionTestBase {
|
|||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Async.Task CanPost_Existing() {
|
public async Async.Task CanPost_Existing() {
|
||||||
var containerName = "test";
|
var containerName = Container.Parse("test");
|
||||||
var client = GetContainerClient(containerName);
|
var client = GetContainerClient(containerName);
|
||||||
await client.CreateIfNotExistsAsync();
|
await client.CreateIfNotExistsAsync();
|
||||||
|
|
||||||
var metadata = new Dictionary<string, string> { { "some", "value" } };
|
var metadata = new Dictionary<string, string> { { "some", "value" } };
|
||||||
var msg = TestHttpRequestData.FromJson("POST", new ContainerCreate(new Container(containerName), metadata));
|
var msg = TestHttpRequestData.FromJson("POST", new ContainerCreate(containerName, metadata));
|
||||||
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
||||||
var func = new ContainersFunction(Logger, auth, Context);
|
var func = new ContainersFunction(Logger, auth, Context);
|
||||||
@ -110,13 +110,13 @@ public abstract class ContainersTestBase : FunctionTestBase {
|
|||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Async.Task Get_Existing() {
|
public async Async.Task Get_Existing() {
|
||||||
var containerName = "test";
|
var containerName = Container.Parse("test");
|
||||||
{
|
{
|
||||||
var client = GetContainerClient(containerName);
|
var client = GetContainerClient(containerName);
|
||||||
await client.CreateIfNotExistsAsync();
|
await client.CreateIfNotExistsAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
var msg = TestHttpRequestData.FromJson("GET", new ContainerGet(new Container(containerName)));
|
var msg = TestHttpRequestData.FromJson("GET", new ContainerGet(containerName));
|
||||||
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
||||||
var func = new ContainersFunction(Logger, auth, Context);
|
var func = new ContainersFunction(Logger, auth, Context);
|
||||||
@ -130,7 +130,8 @@ public abstract class ContainersTestBase : FunctionTestBase {
|
|||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Async.Task Get_Missing_Fails() {
|
public async Async.Task Get_Missing_Fails() {
|
||||||
var msg = TestHttpRequestData.FromJson("GET", new ContainerGet(new Container("container")));
|
var container = Container.Parse("container");
|
||||||
|
var msg = TestHttpRequestData.FromJson("GET", new ContainerGet(container));
|
||||||
|
|
||||||
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
var auth = new TestEndpointAuthorization(RequestType.User, Logger, Context);
|
||||||
var func = new ContainersFunction(Logger, auth, Context);
|
var func = new ContainersFunction(Logger, auth, Context);
|
||||||
@ -142,8 +143,8 @@ public abstract class ContainersTestBase : FunctionTestBase {
|
|||||||
public async Async.Task List_Existing() {
|
public async Async.Task List_Existing() {
|
||||||
var meta1 = new Dictionary<string, string> { { "key1", "value1" } };
|
var meta1 = new Dictionary<string, string> { { "key1", "value1" } };
|
||||||
var meta2 = new Dictionary<string, string> { { "key2", "value2" } };
|
var meta2 = new Dictionary<string, string> { { "key2", "value2" } };
|
||||||
await GetContainerClient("one").CreateIfNotExistsAsync(metadata: meta1);
|
await GetContainerClient(Container.Parse("one")).CreateIfNotExistsAsync(metadata: meta1);
|
||||||
await GetContainerClient("two").CreateIfNotExistsAsync(metadata: meta2);
|
await GetContainerClient(Container.Parse("two")).CreateIfNotExistsAsync(metadata: meta2);
|
||||||
|
|
||||||
var msg = TestHttpRequestData.Empty("GET"); // this means list all
|
var msg = TestHttpRequestData.Empty("GET"); // this means list all
|
||||||
|
|
||||||
@ -154,7 +155,7 @@ public abstract class ContainersTestBase : FunctionTestBase {
|
|||||||
|
|
||||||
var list = BodyAs<ContainerInfoBase[]>(result);
|
var list = BodyAs<ContainerInfoBase[]>(result);
|
||||||
// other tests can run in parallel, so filter to just our containers:
|
// other tests can run in parallel, so filter to just our containers:
|
||||||
var cs = list.Where(ci => ci.Name.ContainerName.StartsWith(Context.ServiceConfiguration.OneFuzzStoragePrefix)).ToList();
|
var cs = list.Where(ci => ci.Name.String.StartsWith(Context.ServiceConfiguration.OneFuzzStoragePrefix)).ToList();
|
||||||
Assert.Equal(2, cs.Count);
|
Assert.Equal(2, cs.Count);
|
||||||
|
|
||||||
// ensure correct metadata was returned.
|
// ensure correct metadata was returned.
|
||||||
|
@ -72,7 +72,8 @@ public abstract class DownloadTestBase : FunctionTestBase {
|
|||||||
[Fact]
|
[Fact]
|
||||||
public async Async.Task Download_RedirectsToResult_WithLocationHeader() {
|
public async Async.Task Download_RedirectsToResult_WithLocationHeader() {
|
||||||
// set up a file to download
|
// set up a file to download
|
||||||
var container = GetContainerClient("xxx");
|
var containerName = Container.Parse("xxx");
|
||||||
|
var container = GetContainerClient(containerName);
|
||||||
await container.CreateAsync();
|
await container.CreateAsync();
|
||||||
await container.UploadBlobAsync("yyy", new BinaryData("content"));
|
await container.UploadBlobAsync("yyy", new BinaryData("content"));
|
||||||
|
|
||||||
|
@ -15,9 +15,9 @@ class TestCreds : ICreds {
|
|||||||
|
|
||||||
private readonly Guid _subscriptionId;
|
private readonly Guid _subscriptionId;
|
||||||
private readonly string _resourceGroup;
|
private readonly string _resourceGroup;
|
||||||
private readonly string _region;
|
private readonly Region _region;
|
||||||
|
|
||||||
public TestCreds(Guid subscriptionId, string resourceGroup, string region) {
|
public TestCreds(Guid subscriptionId, string resourceGroup, Region region) {
|
||||||
_subscriptionId = subscriptionId;
|
_subscriptionId = subscriptionId;
|
||||||
_resourceGroup = resourceGroup;
|
_resourceGroup = resourceGroup;
|
||||||
_region = region;
|
_region = region;
|
||||||
@ -26,8 +26,8 @@ class TestCreds : ICreds {
|
|||||||
public ArmClient ArmClient => null!;
|
public ArmClient ArmClient => null!;
|
||||||
// we have to return something in some test cases, even if it isn’t used
|
// we have to return something in some test cases, even if it isn’t used
|
||||||
|
|
||||||
public Task<string> GetBaseRegion() => Task.FromResult(_region);
|
public Task<Region> GetBaseRegion() => Task.FromResult(_region);
|
||||||
public Task<IReadOnlyList<string>> GetRegions() => Task.FromResult<IReadOnlyList<string>>(new[] { _region });
|
public Task<IReadOnlyList<Region>> GetRegions() => Task.FromResult<IReadOnlyList<Region>>(new[] { _region });
|
||||||
|
|
||||||
public string GetBaseResourceGroup() => _resourceGroup;
|
public string GetBaseResourceGroup() => _resourceGroup;
|
||||||
|
|
||||||
|
@ -48,7 +48,8 @@ public abstract class InfoTestBase : FunctionTestBase {
|
|||||||
// store the instance ID in the expected location:
|
// store the instance ID in the expected location:
|
||||||
// for production this is done by the deploy script
|
// for production this is done by the deploy script
|
||||||
var instanceId = Guid.NewGuid().ToString();
|
var instanceId = Guid.NewGuid().ToString();
|
||||||
var containerClient = GetContainerClient("base-config");
|
var baseConfigContainer = WellKnownContainers.BaseConfig;
|
||||||
|
var containerClient = GetContainerClient(baseConfigContainer);
|
||||||
await containerClient.CreateAsync();
|
await containerClient.CreateAsync();
|
||||||
await containerClient.GetBlobClient("instance_id").UploadAsync(new BinaryData(instanceId));
|
await containerClient.GetBlobClient("instance_id").UploadAsync(new BinaryData(instanceId));
|
||||||
|
|
||||||
|
@ -169,7 +169,7 @@ public abstract class JobsTestBase : FunctionTestBase {
|
|||||||
Assert.NotNull(job.Config.Logs);
|
Assert.NotNull(job.Config.Logs);
|
||||||
Assert.Empty(new Uri(job.Config.Logs!).Query);
|
Assert.Empty(new Uri(job.Config.Logs!).Query);
|
||||||
|
|
||||||
var container = Assert.Single(await Context.Containers.GetContainers(StorageType.Corpus), c => c.Key.Contains(job.JobId.ToString()));
|
var container = Assert.Single(await Context.Containers.GetContainers(StorageType.Corpus), c => c.Key.String.Contains(job.JobId.ToString()));
|
||||||
var metadata = Assert.Single(container.Value);
|
var metadata = Assert.Single(container.Value);
|
||||||
Assert.Equal(new KeyValuePair<string, string>("container_type", "logs"), metadata);
|
Assert.Equal(new KeyValuePair<string, string>("container_type", "logs"), metadata);
|
||||||
}
|
}
|
||||||
|
@ -35,15 +35,15 @@ public abstract class FunctionTestBase : IAsyncLifetime {
|
|||||||
|
|
||||||
private readonly Guid _subscriptionId = Guid.NewGuid();
|
private readonly Guid _subscriptionId = Guid.NewGuid();
|
||||||
private readonly string _resourceGroup = "FakeResourceGroup";
|
private readonly string _resourceGroup = "FakeResourceGroup";
|
||||||
private readonly string _region = "fakeregion";
|
private readonly Region _region = Region.Parse("fakeregion");
|
||||||
|
|
||||||
protected ILogTracer Logger { get; }
|
protected ILogTracer Logger { get; }
|
||||||
|
|
||||||
protected TestContext Context { get; }
|
protected TestContext Context { get; }
|
||||||
|
|
||||||
private readonly BlobServiceClient _blobClient;
|
private readonly BlobServiceClient _blobClient;
|
||||||
protected BlobContainerClient GetContainerClient(string container)
|
protected BlobContainerClient GetContainerClient(Container container)
|
||||||
=> _blobClient.GetBlobContainerClient(_storagePrefix + container);
|
=> _blobClient.GetBlobContainerClient(_storagePrefix + container.String);
|
||||||
|
|
||||||
public FunctionTestBase(ITestOutputHelper output, IStorage storage) {
|
public FunctionTestBase(ITestOutputHelper output, IStorage storage) {
|
||||||
Logger = new TestLogTracer(output);
|
Logger = new TestLogTracer(output);
|
||||||
|
@ -91,6 +91,11 @@ namespace Tests {
|
|||||||
where PoolName.TryParse(name.Get, out _)
|
where PoolName.TryParse(name.Get, out _)
|
||||||
select PoolName.Parse(name.Get);
|
select PoolName.Parse(name.Get);
|
||||||
|
|
||||||
|
public static Gen<Region> RegionGen { get; }
|
||||||
|
= from name in Arb.Generate<NonEmptyString>()
|
||||||
|
where Region.TryParse(name.Get, out _)
|
||||||
|
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<Guid?, DateTimeOffset, string, bool, bool, bool>>>()
|
||||||
from poolName in PoolNameGen
|
from poolName in PoolNameGen
|
||||||
@ -107,39 +112,47 @@ namespace Tests {
|
|||||||
DeleteRequested: arg.Item2.Item5,
|
DeleteRequested: arg.Item2.Item5,
|
||||||
DebugKeepNode: arg.Item2.Item6);
|
DebugKeepNode: arg.Item2.Item6);
|
||||||
|
|
||||||
public static Gen<ProxyForward> ProxyForward() {
|
public static Gen<ProxyForward> ProxyForward { get; } =
|
||||||
return Arb.Generate<Tuple<Tuple<string, long, Guid, Guid, Guid?, long>, Tuple<IPv4Address, DateTimeOffset>>>().Select(
|
from region in RegionGen
|
||||||
arg =>
|
from port in Gen.Choose(0, ushort.MaxValue)
|
||||||
new ProxyForward(
|
from scalesetId in Arb.Generate<Guid>()
|
||||||
Region: arg.Item1.Item1,
|
from machineId in Arb.Generate<Guid>()
|
||||||
Port: arg.Item1.Item2,
|
from proxyId in Arb.Generate<Guid?>()
|
||||||
ScalesetId: arg.Item1.Item3,
|
from dstPort in Gen.Choose(0, ushort.MaxValue)
|
||||||
MachineId: arg.Item1.Item4,
|
from dstIp in Arb.Generate<IPv4Address>()
|
||||||
ProxyId: arg.Item1.Item5,
|
from endTime in Arb.Generate<DateTimeOffset>()
|
||||||
DstPort: arg.Item1.Item6,
|
select new ProxyForward(
|
||||||
DstIp: arg.Item2.Item1.ToString(),
|
Region: region,
|
||||||
EndTime: arg.Item2.Item2
|
Port: port,
|
||||||
)
|
ScalesetId: scalesetId,
|
||||||
);
|
MachineId: machineId,
|
||||||
}
|
ProxyId: proxyId,
|
||||||
|
DstPort: dstPort,
|
||||||
|
DstIp: dstIp.ToString(),
|
||||||
|
EndTime: endTime);
|
||||||
|
|
||||||
public static Gen<Proxy> Proxy() {
|
public static Gen<Proxy> Proxy { get; } =
|
||||||
return Arb.Generate<Tuple<Tuple<string, Guid, DateTimeOffset?, VmState, Authentication, string?, Error?>, Tuple<string, ProxyHeartbeat?, bool>>>().Select(
|
from region in RegionGen
|
||||||
arg =>
|
from proxyId in Arb.Generate<Guid>()
|
||||||
new Proxy(
|
from createdTimestamp in Arb.Generate<DateTimeOffset?>()
|
||||||
Region: arg.Item1.Item1,
|
from state in Arb.Generate<VmState>()
|
||||||
ProxyId: arg.Item1.Item2,
|
from auth in Arb.Generate<Authentication>()
|
||||||
CreatedTimestamp: arg.Item1.Item3,
|
from ip in Arb.Generate<string>()
|
||||||
State: arg.Item1.Item4,
|
from error in Arb.Generate<Error?>()
|
||||||
Auth: arg.Item1.Item5,
|
from version in Arb.Generate<string>()
|
||||||
Ip: arg.Item1.Item6,
|
from heartbeat in Arb.Generate<ProxyHeartbeat?>()
|
||||||
Error: arg.Item1.Item7,
|
from outdated in Arb.Generate<bool>()
|
||||||
Version: arg.Item2.Item1,
|
select new Proxy(
|
||||||
Heartbeat: arg.Item2.Item2,
|
Region: region,
|
||||||
Outdated: arg.Item2.Item3
|
ProxyId: proxyId,
|
||||||
)
|
CreatedTimestamp: createdTimestamp,
|
||||||
);
|
State: state,
|
||||||
}
|
Auth: auth,
|
||||||
|
Ip: ip,
|
||||||
|
Error: error,
|
||||||
|
Version: version,
|
||||||
|
Heartbeat: heartbeat,
|
||||||
|
Outdated: outdated);
|
||||||
|
|
||||||
public static Gen<EventMessage> EventMessage() {
|
public static Gen<EventMessage> EventMessage() {
|
||||||
return Arb.Generate<Tuple<Guid, BaseEvent, Guid, string>>().Select(
|
return Arb.Generate<Tuple<Guid, BaseEvent, Guid, string>>().Select(
|
||||||
@ -219,10 +232,11 @@ 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, string, string>,
|
Tuple<Guid, ScalesetState, Authentication?, string, 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 poolName in PoolNameGen
|
from poolName in PoolNameGen
|
||||||
|
from region in RegionGen
|
||||||
select new Scaleset(
|
select new Scaleset(
|
||||||
PoolName: poolName,
|
PoolName: poolName,
|
||||||
ScalesetId: arg.Item1.Item1,
|
ScalesetId: arg.Item1.Item1,
|
||||||
@ -230,7 +244,7 @@ namespace Tests {
|
|||||||
Auth: arg.Item1.Item3,
|
Auth: arg.Item1.Item3,
|
||||||
VmSku: arg.Item1.Item4,
|
VmSku: arg.Item1.Item4,
|
||||||
Image: arg.Item1.Item5,
|
Image: arg.Item1.Item5,
|
||||||
Region: arg.Item1.Item6,
|
Region: region,
|
||||||
|
|
||||||
Size: arg.Item2.Item1,
|
Size: arg.Item2.Item1,
|
||||||
SpotInstances: arg.Item2.Item2,
|
SpotInstances: arg.Item2.Item2,
|
||||||
@ -346,11 +360,12 @@ namespace Tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Gen<Container> Container() {
|
public static Gen<Container> ContainerGen { get; } =
|
||||||
return Arb.Generate<Tuple<NonNull<string>>>().Select(
|
from len in Gen.Choose(3, 63)
|
||||||
arg => new Container(string.Join("", arg.Item1.Get.Where(c => char.IsLetterOrDigit(c) || c == '-'))!)
|
from name in Gen.ArrayOf(len, Gen.Elements<char>("abcdefghijklmnopqrstuvwxyz0123456789-"))
|
||||||
);
|
let nameString = new string(name)
|
||||||
}
|
where Container.TryParse(nameString, out var _)
|
||||||
|
select Container.Parse(nameString);
|
||||||
|
|
||||||
public static Gen<NotificationTemplate> NotificationTemplate() {
|
public static Gen<NotificationTemplate> NotificationTemplate() {
|
||||||
return Gen.OneOf(new[] {
|
return Gen.OneOf(new[] {
|
||||||
@ -411,11 +426,11 @@ namespace Tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static Arbitrary<ProxyForward> ProxyForward() {
|
public static Arbitrary<ProxyForward> ProxyForward() {
|
||||||
return Arb.From(OrmGenerators.ProxyForward());
|
return Arb.From(OrmGenerators.ProxyForward);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Arbitrary<Proxy> Proxy() {
|
public static Arbitrary<Proxy> Proxy() {
|
||||||
return Arb.From(OrmGenerators.Proxy());
|
return Arb.From(OrmGenerators.Proxy);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Arbitrary<EventMessage> EventMessage() {
|
public static Arbitrary<EventMessage> EventMessage() {
|
||||||
@ -458,9 +473,12 @@ namespace Tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static Arbitrary<Container> Container() {
|
public static Arbitrary<Container> Container() {
|
||||||
return Arb.From(OrmGenerators.Container());
|
return Arb.From(OrmGenerators.ContainerGen);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Arbitrary<Region> Region() {
|
||||||
|
return Arb.From(OrmGenerators.RegionGen);
|
||||||
|
}
|
||||||
|
|
||||||
public static Arbitrary<NotificationTemplate> NotificationTemplate() {
|
public static Arbitrary<NotificationTemplate> NotificationTemplate() {
|
||||||
return Arb.From(OrmGenerators.NotificationTemplate());
|
return Arb.From(OrmGenerators.NotificationTemplate());
|
||||||
|
@ -265,15 +265,15 @@ namespace Tests {
|
|||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void TestContainerSerialization() {
|
public void TestContainerSerialization() {
|
||||||
var container = new Container("abc-123");
|
var container = Container.Parse("abc-123");
|
||||||
var expected = new Entity3(123, "abc", container);
|
var expected = new Entity3(123, "abc", container);
|
||||||
var converter = new EntityConverter();
|
var converter = new EntityConverter();
|
||||||
|
|
||||||
var tableEntity = converter.ToTableEntity(expected);
|
var tableEntity = converter.ToTableEntity(expected);
|
||||||
var actual = converter.ToRecord<Entity3>(tableEntity);
|
var actual = converter.ToRecord<Entity3>(tableEntity);
|
||||||
|
|
||||||
Assert.Equal(expected.Container.ContainerName, actual.Container.ContainerName);
|
Assert.Equal(expected.Container, actual.Container);
|
||||||
Assert.Equal(expected.Container.ContainerName, tableEntity.GetString("container"));
|
Assert.Equal(expected.Container.String, tableEntity.GetString("container"));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@ -288,7 +288,7 @@ namespace Tests {
|
|||||||
|
|
||||||
Assert.Equal(123, entity?.Id);
|
Assert.Equal(123, entity?.Id);
|
||||||
Assert.Equal("abc", entity?.TheName);
|
Assert.Equal("abc", entity?.TheName);
|
||||||
Assert.Equal("abc-123", entity?.Container.ContainerName);
|
Assert.Equal("abc-123", entity?.Container.String);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -300,7 +300,7 @@ namespace Tests {
|
|||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void TestPartitionKeyIsRowKey() {
|
public void TestPartitionKeyIsRowKey() {
|
||||||
var container = new Container("abc-123");
|
var container = Container.Parse("abc-123");
|
||||||
var expected = new Entity4(123, "abc", container);
|
var expected = new Entity4(123, "abc", container);
|
||||||
var converter = new EntityConverter();
|
var converter = new EntityConverter();
|
||||||
|
|
||||||
@ -310,8 +310,8 @@ namespace Tests {
|
|||||||
|
|
||||||
var actual = converter.ToRecord<Entity4>(tableEntity);
|
var actual = converter.ToRecord<Entity4>(tableEntity);
|
||||||
|
|
||||||
Assert.Equal(expected.Container.ContainerName, actual.Container.ContainerName);
|
Assert.Equal(expected.Container, actual.Container);
|
||||||
Assert.Equal(expected.Container.ContainerName, tableEntity.GetString("container"));
|
Assert.Equal(expected.Container.String, tableEntity.GetString("container"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -25,7 +25,9 @@ public class SchedulerTests {
|
|||||||
TargetEnv: new Dictionary<string, string>(),
|
TargetEnv: new Dictionary<string, string>(),
|
||||||
TargetOptions: new List<string>()),
|
TargetOptions: new List<string>()),
|
||||||
Pool: new TaskPool(1, PoolName.Parse("pool")),
|
Pool: new TaskPool(1, PoolName.Parse("pool")),
|
||||||
Containers: new List<TaskContainers> { new TaskContainers(ContainerType.Setup, new Container("setup")) },
|
Containers: new List<TaskContainers> {
|
||||||
|
new TaskContainers(ContainerType.Setup, Container.Parse("setup"))
|
||||||
|
},
|
||||||
Colocate: true
|
Colocate: true
|
||||||
|
|
||||||
),
|
),
|
||||||
@ -105,7 +107,7 @@ public class SchedulerTests {
|
|||||||
var tasks = BuildTasks(100).Select((task, i) => {
|
var tasks = BuildTasks(100).Select((task, i) => {
|
||||||
var containers = new List<TaskContainers>(task.Config.Containers!);
|
var containers = new List<TaskContainers>(task.Config.Containers!);
|
||||||
if (i % 4 == 0) {
|
if (i % 4 == 0) {
|
||||||
containers[0] = containers[0] with { Name = new Container("setup2") };
|
containers[0] = containers[0] with { Name = Container.Parse("setup2") };
|
||||||
}
|
}
|
||||||
return task with {
|
return task with {
|
||||||
JobId = i % 2 == 0 ? jobId : task.JobId,
|
JobId = i % 2 == 0 ? jobId : task.JobId,
|
||||||
|
@ -101,10 +101,9 @@ public class TimerReproTests {
|
|||||||
Guid.NewGuid(),
|
Guid.NewGuid(),
|
||||||
Guid.Empty,
|
Guid.Empty,
|
||||||
new ReproConfig(
|
new ReproConfig(
|
||||||
new Container(String.Empty),
|
Container.Parse("container"),
|
||||||
String.Empty,
|
"",
|
||||||
0
|
0),
|
||||||
),
|
|
||||||
null,
|
null,
|
||||||
Os.Linux,
|
Os.Linux,
|
||||||
VmState.Init,
|
VmState.Init,
|
||||||
|
@ -19,4 +19,18 @@ public class ValidatedStringTests {
|
|||||||
var result = JsonSerializer.Serialize(new ThingContainingPoolName(PoolName.Parse("is-a-pool")));
|
var result = JsonSerializer.Serialize(new ThingContainingPoolName(PoolName.Parse("is-a-pool")));
|
||||||
Assert.Equal("{\"PoolName\":\"is-a-pool\"}", result);
|
Assert.Equal("{\"PoolName\":\"is-a-pool\"}", result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("x", false)] // too short
|
||||||
|
[InlineData("xy", false)] // too short
|
||||||
|
[InlineData("xyz", true)]
|
||||||
|
[InlineData("-container", false)] // can't start with hyphen
|
||||||
|
[InlineData("container-", true)] // can end with hyphen
|
||||||
|
[InlineData("container-name", true)] // can have middle hyphen
|
||||||
|
[InlineData("container--name", false)] // can't have two consecutive hyphens
|
||||||
|
[InlineData("container-Name", false)] // can't have capitals
|
||||||
|
[InlineData("container-name-09", true)] // can have numbers
|
||||||
|
public void ContainerNames(string name, bool valid) {
|
||||||
|
Assert.Equal(valid, Container.TryParse(name, out var _));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user