migrate timer_proxy part 2 (#1836)

This commit is contained in:
Cheick Keita
2022-04-25 10:14:15 -07:00
committed by GitHub
parent 66796148c5
commit 3a93de4801
20 changed files with 305 additions and 66 deletions

View File

@ -8,6 +8,7 @@
<WarningLevel>5</WarningLevel>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Faithlife.Utility" Version="0.12.2" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Storage" Version="5.0.0" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.EventGrid" Version="2.1.0" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http" Version="3.0.13" />

View File

@ -1,4 +1,4 @@
using Microsoft.OneFuzz.Service.OneFuzzLib.Orm;
using Microsoft.OneFuzz.Service.OneFuzzLib.Orm;
using System.Text.Json;
using System.Text.Json.Serialization;
using PoolName = System.String;

View File

@ -95,7 +95,7 @@ public record ProxyHeartbeat
DateTimeOffset TimeStamp
);
public partial record Node
public record Node
(
DateTimeOffset? InitializedAt,
[PartitionKey] PoolName PoolName,
@ -111,27 +111,40 @@ public partial record Node
) : StatefulEntityBase<NodeState>(State);
public partial record ProxyForward
public record Forward
(
int SrcPort,
int DstPort,
string DstIp
);
public record ProxyForward
(
[PartitionKey] Region Region,
int Port,
Guid ScalesetId,
Guid MachineId,
Guid? ProxyId,
[RowKey] int DstPort,
int SrcPort,
string DstIp
string DstIp,
DateTimeOffset EndTime
) : EntityBase();
public partial record ProxyConfig
public record ProxyConfig
(
Uri Url,
string Notification,
Uri Notification,
Region Region,
Guid? ProxyId,
List<ProxyForward> Forwards,
List<Forward> Forwards,
string InstanceTelemetryKey,
string MicrosoftTelemetryKey
string MicrosoftTelemetryKey,
Guid InstanceId
);
public partial record Proxy
public record Proxy
(
[PartitionKey] Region Region,
[RowKey] Guid ProxyId,

View File

@ -1,4 +1,4 @@
// to avoid collision with Task in model.cs
// to avoid collision with Task in model.cs
global using Async = System.Threading.Tasks;
global using System;

View File

@ -36,6 +36,8 @@ public interface IServiceConfig
public string? OneFuzzResourceGroup { get; }
public string? OneFuzzTelemetry { get; }
public string OnefuzzVersion { get; }
}
public class ServiceConfiguration : IServiceConfig
@ -79,4 +81,5 @@ public class ServiceConfiguration : IServiceConfig
public string? OneFuzzOwner { get => Environment.GetEnvironmentVariable("ONEFUZZ_OWNER"); }
public string? OneFuzzResourceGroup { get => Environment.GetEnvironmentVariable("ONEFUZZ_RESOURCE_GROUP"); }
public string? OneFuzzTelemetry { get => Environment.GetEnvironmentVariable("ONEFUZZ_TELEMETRY"); }
public string OnefuzzVersion { get => Environment.GetEnvironmentVariable("ONEFUZZ_VERSION") ?? "0.0.0"; }
}

View File

@ -0,0 +1,16 @@
namespace Microsoft.OneFuzz.Service;
using System.Security.Cryptography;
public class Auth
{
public static Authentication BuildAuth()
{
var rsa = RSA.Create(2048);
string header = "-----BEGIN RSA PRIVATE KEY-----";
string footer = "-----END RSA PRIVATE KEY-----";
var privateKey = $"{header}\n{Convert.ToBase64String(rsa.ExportRSAPrivateKey())}\n{footer}";
var publiceKey = $"{header}\n{Convert.ToBase64String(rsa.ExportRSAPublicKey())}\n{footer}";
return new Authentication(Guid.NewGuid().ToString(), publiceKey, privateKey);
}
}

View File

@ -3,17 +3,20 @@ using Azure.ResourceManager;
using Azure.Storage.Blobs;
using Azure.Storage;
using Azure;
using Azure.Storage.Sas;
namespace Microsoft.OneFuzz.Service;
public interface IContainers
{
public Task<IEnumerable<byte>?> GetBlob(Container container, string name, StorageType storageType);
public Task<BinaryData?> GetBlob(Container container, string name, StorageType storageType);
public Async.Task<BlobContainerClient?> FindContainer(Container container, StorageType storageType);
public Uri GetFileSasUrl(Container container, string name, StorageType storageType, bool read = false, bool add = false, bool create = false, bool write = false, bool delete = false, bool delete_previous_version = false, bool tag = false, int days = 30, int hours = 0, int minutes = 0);
public Async.Task<Uri?> GetFileSasUrl(Container container, string name, StorageType storageType, BlobSasPermissions permissions, TimeSpan? duration = null);
Async.Task saveBlob(Container container, string v1, string v2, StorageType config);
Task<Guid> GetInstanceId();
}
public class Containers : IContainers
@ -29,7 +32,7 @@ public class Containers : IContainers
_creds = creds;
_armClient = creds.ArmClient;
}
public async Task<IEnumerable<byte>?> GetBlob(Container container, string name, StorageType storageType)
public async Task<BinaryData?> GetBlob(Container container, string name, StorageType storageType)
{
var client = await FindContainer(container, storageType);
@ -41,7 +44,7 @@ public class Containers : IContainers
try
{
return (await client.GetBlobClient(name).DownloadContentAsync())
.Value.Content.ToArray();
.Value.Content;
}
catch (RequestFailedException)
{
@ -85,9 +88,59 @@ public class Containers : IContainers
return new Uri($"https://{accountName}.blob.core.windows.net/");
}
public Uri GetFileSasUrl(Container container, string name, StorageType storageType, bool read = false, bool add = false, bool create = false, bool write = false, bool delete = false, bool delete_previous_version = false, bool tag = false, int days = 30, int hours = 0, int minutes = 0)
public async Async.Task<Uri?> GetFileSasUrl(Container container, string name, StorageType storageType, BlobSasPermissions permissions, TimeSpan? duration = null)
{
throw new NotImplementedException();
var client = await FindContainer(container, storageType) ?? throw new Exception($"unable to find container: {container.ContainerName} - {storageType}");
var (accountName, accountKey) = _storage.GetStorageAccountNameAndKey(client.AccountName);
var (startTime, endTime) = SasTimeWindow(duration ?? TimeSpan.FromDays(30));
var sasBuilder = new BlobSasBuilder(permissions, endTime)
{
StartsOn = startTime,
BlobContainerName = container.ContainerName,
BlobName = name
};
var sasUrl = client.GetBlobClient(name).GenerateSasUri(sasBuilder);
return sasUrl;
}
public (DateTimeOffset, DateTimeOffset) SasTimeWindow(TimeSpan timeSpan)
{
// SAS URLs are valid 6 hours earlier, primarily to work around dev
// workstations having out-of-sync time. Additionally, SAS URLs are stopped
// 15 minutes later than requested based on "Be careful with SAS start time"
// guidance.
// Ref: https://docs.microsoft.com/en-us/azure/storage/common/storage-sas-overview
var SAS_START_TIME_DELTA = TimeSpan.FromHours(6);
var SAS_END_TIME_DELTA = TimeSpan.FromMinutes(6);
// SAS_START_TIME_DELTA = datetime.timedelta(hours = 6)
//SAS_END_TIME_DELTA = datetime.timedelta(minutes = 15)
var now = DateTimeOffset.UtcNow;
var start = now - SAS_START_TIME_DELTA;
var expiry = now + timeSpan + SAS_END_TIME_DELTA;
return (start, expiry);
}
public async System.Threading.Tasks.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}");
await client.UploadBlobAsync(name, new BinaryData(data));
}
public async Async.Task<Guid> GetInstanceId()
{
var blob = await GetBlob(new Container("base-config"), "instance_id", StorageType.Config);
if (blob == null)
{
throw new System.Exception("Blob Not Found");
}
return System.Guid.Parse(blob.ToString());
}
}

View File

@ -15,12 +15,11 @@ public class ConfigOperations : Orm<InstanceConfig>, IConfigOperations
{
private readonly IEvents _events;
private readonly ILogTracer _log;
private readonly IServiceConfig _config;
public ConfigOperations(IStorage storage, IEvents events, ILogTracer log, IServiceConfig config) : base(storage, log, config)
{
_events = events;
_log = log;
_config = config;
}
public async Task<InstanceConfig> Fetch()

View File

@ -25,12 +25,6 @@ public partial class TimerProxy
_subnet = subnet;
}
private static Guid GenerateGuidv5(Guid nameSpace, string name)
{
throw new NotImplementedException();
}
public static async Async.Task<Network> Create(string region, ICreds creds, IConfigOperations configOperations, ISubnet subnet)
{
var group = creds.GetBaseResourceGroup();
@ -50,7 +44,7 @@ public partial class TimerProxy
}
else
{
var networkId = GenerateGuidv5(NETWORK_GUID_NAMESPACE, string.Join("|", networkConfig.AddressSpace, networkConfig.Subnet));
var networkId = Faithlife.Utility.GuidUtility.Create(NETWORK_GUID_NAMESPACE, string.Join("|", networkConfig.AddressSpace, networkConfig.Subnet), 5);
name = $"{region}-{networkId}";
}

View File

@ -1,5 +1,6 @@
using System.Text.Json;
using ApiService.OneFuzzLib.Orm;
using Azure.Storage.Sas;
namespace Microsoft.OneFuzz.Service;
@ -10,7 +11,6 @@ public interface INotificationOperations
public class NotificationOperations : Orm<Notification>, INotificationOperations
{
private ILogTracer _log;
private IReports _reports;
private ITaskOperations _taskOperations;
@ -23,7 +23,7 @@ public class NotificationOperations : Orm<Notification>, INotificationOperations
public NotificationOperations(ILogTracer log, IStorage storage, IReports reports, ITaskOperations taskOperations, IContainers containers, IQueue queue, IEvents events, IServiceConfig config)
: base(storage, log, config)
{
_log = log;
_reports = reports;
_taskOperations = taskOperations;
_containers = containers;
@ -76,9 +76,9 @@ public class NotificationOperations : Orm<Notification>, INotificationOperations
{
if (containers.Contains(container.ContainerName))
{
_log.Info($"queuing input {container.ContainerName} {filename} {task.TaskId}");
var url = _containers.GetFileSasUrl(container, filename, StorageType.Corpus, read: true, delete: true);
await _queue.SendMessage(task.TaskId.ToString(), System.Text.Encoding.UTF8.GetBytes(url.ToString()), StorageType.Corpus);
_logTracer.Info($"queuing input {container.ContainerName} {filename} {task.TaskId}");
var url = _containers.GetFileSasUrl(container, filename, StorageType.Corpus, BlobSasPermissions.Read | BlobSasPermissions.Delete);
await _queue.SendMessage(task.TaskId.ToString(), System.Text.Encoding.UTF8.GetBytes(url?.ToString() ?? ""), StorageType.Corpus);
}
}
@ -125,7 +125,7 @@ public class NotificationOperations : Orm<Notification>, INotificationOperations
return await _taskOperations.GetByJobIdAndTaskId(report.CrashTestResult.NoReproReport.JobId, report.CrashTestResult.NoReproReport.TaskId);
}
_log.Error($"unable to find crash_report or no repro entry for report: {JsonSerializer.Serialize(report)}");
_logTracer.Error($"unable to find crash_report or no repro entry for report: {JsonSerializer.Serialize(report)}");
return null;
}

View File

@ -72,8 +72,10 @@ namespace Microsoft.OneFuzz.Service
return !active_regions.Contains(nsg_region) && nsg_region == nsg_name;
}
// Returns True if deletion completed (thus resource not found) or successfully started.
// Returns False if failed to start deletion.
/// <summary>
/// Returns True if deletion completed (thus resource not found) or successfully started.
/// Returns False if failed to start deletion.
/// </summary>
public async Async.Task<bool> StartDeleteNsg(string name)
{
_logTracer.Info($"deleting nsg: {name}");

View File

@ -0,0 +1,34 @@
using ApiService.OneFuzzLib.Orm;
namespace Microsoft.OneFuzz.Service;
public interface IProxyForwardOperations : IOrm<ProxyForward>
{
IAsyncEnumerable<ProxyForward> SearchForward(Guid? scalesetId = null, string? region = null, Guid? machineId = null, Guid? proxyId = null, int? dstPort = null);
}
public class ProxyForwardOperations : Orm<ProxyForward>, IProxyForwardOperations
{
public ProxyForwardOperations(IStorage storage, ILogTracer logTracer, IServiceConfig config) : base(storage, logTracer, config)
{
}
public IAsyncEnumerable<ProxyForward> SearchForward(Guid? scalesetId = null, string? region = null, Guid? machineId = null, Guid? proxyId = null, int? dstPort = null)
{
var conditions =
new[] {
scalesetId != null ? $"scaleset_id eq '{scalesetId}'" : null,
region != null ? $"region eq '{region}'" : null ,
machineId != null ? $"machine_id eq '{machineId}'" : null ,
proxyId != null ? $"proxy_id eq '{proxyId}'" : null ,
dstPort != null ? $"dsp_port eq {dstPort }" : null ,
}.Where(x => x != null);
var filter = string.Join(" and ", conditions);
return QueryAsync(filter);
}
}

View File

@ -1,4 +1,5 @@
using ApiService.OneFuzzLib.Orm;
using Azure.Storage.Sas;
using System.Threading.Tasks;
namespace Microsoft.OneFuzz.Service;
@ -11,19 +12,27 @@ public interface IProxyOperations : IStatefulOrm<Proxy, VmState>
bool IsAlive(Proxy proxy);
System.Threading.Tasks.Task SaveProxyConfig(Proxy proxy);
bool IsOutdated(Proxy proxy);
System.Threading.Tasks.Task GetOrCreate(string region);
System.Threading.Tasks.Task<Proxy?> GetOrCreate(string region);
}
public class ProxyOperations : StatefulOrm<Proxy, VmState>, IProxyOperations
{
private readonly ILogTracer _log;
private readonly IEvents _events;
private readonly IProxyForwardOperations _proxyForwardOperations;
private readonly IContainers _containers;
private readonly IQueue _queue;
private readonly ICreds _creds;
public ProxyOperations(ILogTracer log, IStorage storage, IEvents events, IServiceConfig config)
: base(storage, log, config)
static TimeSpan PROXY_LIFESPAN = TimeSpan.FromDays(7);
public ProxyOperations(ILogTracer log, IStorage storage, IEvents events, IProxyForwardOperations proxyForwardOperations, IContainers containers, IQueue queue, ICreds creds, IServiceConfig config)
: base(storage, log.WithTag("Component", "scaleset-proxy"), config)
{
_log = log;
_events = events;
_proxyForwardOperations = proxyForwardOperations;
_containers = containers;
_queue = queue;
_creds = creds;
}
public async Task<Proxy?> GetByProxyId(Guid proxyId)
@ -34,26 +43,97 @@ public class ProxyOperations : StatefulOrm<Proxy, VmState>, IProxyOperations
return await data.FirstOrDefaultAsync();
}
public System.Threading.Tasks.Task GetOrCreate(string region)
public async System.Threading.Tasks.Task<Proxy?> GetOrCreate(string region)
{
throw new NotImplementedException();
var proxyList = QueryAsync(filter: $"region eq '{region}' and outdated eq false");
await foreach (var proxy in proxyList)
{
if (IsOutdated(proxy))
{
await Replace(proxy with { Outdated = true });
continue;
}
if (!VmStateHelper.Available().Contains(proxy.State))
{
continue;
}
return proxy;
}
_logTracer.Info($"creating proxy: region:{region}");
var newProxy = new Proxy(region, Guid.NewGuid(), DateTimeOffset.UtcNow, VmState.Init, Auth.BuildAuth(), null, null, _config.OnefuzzVersion, null, false);
await Replace(newProxy);
await _events.SendEvent(new EventProxyCreated(region, newProxy.ProxyId));
return newProxy;
}
public bool IsAlive(Proxy proxy)
{
throw new NotImplementedException();
var tenMinutesAgo = DateTimeOffset.UtcNow - TimeSpan.FromMinutes(10);
if (proxy.Heartbeat != null && proxy.Heartbeat.TimeStamp < tenMinutesAgo)
{
_logTracer.Info($"last heartbeat is more than an 10 minutes old: {proxy.Region} - last heartbeat:{proxy.Heartbeat} compared_to:{tenMinutesAgo}");
return false;
}
if (proxy.Heartbeat != null && proxy.TimeStamp != null && proxy.TimeStamp < tenMinutesAgo)
{
_logTracer.Error($"no heartbeat in the last 10 minutes: {proxy.Region} timestamp: {proxy.TimeStamp} compared_to:{tenMinutesAgo}");
return false;
}
return true;
}
public bool IsOutdated(Proxy proxy)
{
throw new NotImplementedException();
if (!VmStateHelper.Available().Contains(proxy.State))
{
return false;
}
public System.Threading.Tasks.Task SaveProxyConfig(Proxy proxy)
if (proxy.Version != _config.OnefuzzVersion)
{
throw new NotImplementedException();
_logTracer.Info($"mismatch version: proxy:{proxy.Version} service:{_config.OnefuzzVersion} state:{proxy.State}");
return true;
}
if (proxy.CreatedTimestamp != null)
{
if (proxy.CreatedTimestamp < (DateTimeOffset.UtcNow - PROXY_LIFESPAN))
{
_logTracer.Info($"proxy older than 7 days:proxy-created:{proxy.CreatedTimestamp} state:{proxy.State}");
return true;
}
}
return false;
}
public async System.Threading.Tasks.Task SaveProxyConfig(Proxy proxy)
{
var forwards = await GetForwards(proxy);
var url = (await _containers.GetFileSasUrl(new Container("proxy-configs"), $"{proxy.Region}/{proxy.ProxyId}/config.json", StorageType.Config, BlobSasPermissions.Read)).EnsureNotNull("Can't generate file sas");
var proxyConfig = new ProxyConfig(
Url: url,
Notification: _queue.GetQueueSas("proxy", StorageType.Config, QueueSasPermissions.Add).EnsureNotNull("can't generate queue sas"),
Region: proxy.Region,
ProxyId: proxy.ProxyId,
Forwards: forwards,
InstanceTelemetryKey: _config.ApplicationInsightsInstrumentationKey.EnsureNotNull("missing InstrumentationKey"),
MicrosoftTelemetryKey: _config.OneFuzzTelemetry.EnsureNotNull("missing Telemetry"),
InstanceId: await _containers.GetInstanceId());
await _containers.saveBlob(new Container("proxy-configs"), $"{proxy.Region}/{proxy.ProxyId}/config.json", _entityConverter.ToJsonString(proxyConfig), StorageType.Config);
}
public async Async.Task SetState(Proxy proxy, VmState state)
{
if (proxy.State == state)
@ -65,4 +145,23 @@ public class ProxyOperations : StatefulOrm<Proxy, VmState>, IProxyOperations
await _events.SendEvent(new EventProxyStateUpdated(proxy.Region, proxy.ProxyId, proxy.State));
}
public async Async.Task<List<Forward>> GetForwards(Proxy proxy)
{
var forwards = new List<Forward>();
await foreach (var entry in _proxyForwardOperations.SearchForward(region: proxy.Region, proxyId: proxy.ProxyId))
{
if (entry.EndTime < DateTimeOffset.UtcNow)
{
await _proxyForwardOperations.Delete(entry);
}
else
{
forwards.Add(new Forward(entry.Port, entry.DstPort, entry.DstIp));
}
}
return forwards;
}
}

View File

@ -1,5 +1,6 @@
using Azure.Storage;
using Azure.Storage.Queues;
using Azure.Storage.Sas;
using Microsoft.OneFuzz.Service.OneFuzzLib.Orm;
using System.Text.Json;
using System.Threading.Tasks;
@ -9,6 +10,7 @@ public interface IQueue
{
Async.Task SendMessage(string name, byte[] message, StorageType storageType, TimeSpan? visibilityTimeout = null, TimeSpan? timeToLive = null);
Async.Task<bool> QueueObject<T>(string name, T obj, StorageType storageType, TimeSpan? visibilityTimeout);
Uri? GetQueueSas(string name, StorageType storageType, QueueSasPermissions permissions, TimeSpan? duration = null);
}
@ -17,6 +19,8 @@ public class Queue : IQueue
IStorage _storage;
ILogTracer _log;
static TimeSpan DEFAULT_DURATION = TimeSpan.FromDays(30);
public Queue(IStorage storage, ILogTracer log)
{
_storage = storage;
@ -80,4 +84,12 @@ public class Queue : IQueue
return false;
}
}
public Uri? GetQueueSas(string name, StorageType storageType, QueueSasPermissions permissions, TimeSpan? duration)
{
var queue = GetQueue(name, storageType) ?? throw new Exception($"unable to queue object, no such queue: {name}");
var sasaBuilder = new QueueSasBuilder(permissions, DateTimeOffset.UtcNow + (duration ?? DEFAULT_DURATION));
var url = queue.GenerateSasUri(sasaBuilder);
return url;
}
}

View File

@ -41,7 +41,7 @@ public class Reports : IReports
return null;
}
return ParseReportOrRegression(blob, filePath, expectReports);
return ParseReportOrRegression(blob.ToString(), filePath, expectReports);
}
private RegressionReportOrReport? ParseReportOrRegression(string content, string? filePath, bool expectReports = false)

View File

@ -1,6 +1,8 @@
using Azure.ResourceManager.Network;
namespace Microsoft.OneFuzz.Service;
public interface ISubnet
{
System.Threading.Tasks.Task<VirtualNetworkResource?> GetVnet(string vnetName);

View File

@ -1,4 +1,4 @@
using Azure.Data.Tables;
using Azure.Data.Tables;
using System.Reflection;
using System.Linq.Expressions;
using System.Text.Json;
@ -14,7 +14,7 @@ public abstract record EntityBase
public DateTimeOffset? TimeStamp { get; set; }
}
public abstract record StatefulEntityBase<T>([property: JsonIgnore] T state) : EntityBase() where T : Enum;
public abstract record StatefulEntityBase<T>([property: JsonIgnore] T State) : EntityBase() where T : Enum;
/// Indicates that the enum cases should no be renamed
[AttributeUsage(AttributeTargets.Enum)]
@ -147,7 +147,7 @@ public class EntityConverter
});
}
public string ToJsonString<T>(T typedEntity) where T : EntityBase
public string ToJsonString<T>(T typedEntity)
{
var serialized = JsonSerializer.Serialize(typedEntity, _options);
return serialized;

View File

@ -20,14 +20,13 @@ namespace ApiService.OneFuzzLib.Orm
}
public class Orm<T> : IOrm<T> where T : EntityBase
{
IStorage _storage;
EntityConverter _entityConverter;
IServiceConfig _config;
protected ILogTracer _logTracer;
protected readonly IStorage _storage;
protected readonly EntityConverter _entityConverter;
protected readonly ILogTracer _logTracer;
protected readonly IServiceConfig _config;
public Orm(IStorage storage, ILogTracer logTracer, IServiceConfig config)
@ -179,7 +178,7 @@ namespace ApiService.OneFuzzLib.Orm
/// <returns></returns>
public async System.Threading.Tasks.Task<T?> ProcessStateUpdate(T entity)
{
TState state = entity.state;
TState state = entity.State;
var func = _stateFuncs.GetOrAdd(state.ToString(), (string k) =>
typeof(T).GetMethod(k) switch
{
@ -206,13 +205,13 @@ namespace ApiService.OneFuzzLib.Orm
{
for (int i = 0; i < MaxUpdates; i++)
{
var state = entity.state;
var state = entity.State;
var newEntity = await ProcessStateUpdate(entity);
if (newEntity == null)
return null;
if (newEntity.state.Equals(state))
if (newEntity.State.Equals(state))
{
return newEntity;
}

View File

@ -128,6 +128,12 @@
"System.Text.Json": "4.7.2"
}
},
"Faithlife.Utility": {
"type": "Direct",
"requested": "[0.12.2, )",
"resolved": "0.12.2",
"contentHash": "JgMAGj8ekeAzKkagubXqf1UqgfHq89GyA1UQYWbkAe441uRr2Rh2rktkx5Z0LPwmD/aOqu9cxjekD2GZjP8rbw=="
},
"Microsoft.Azure.Functions.Worker": {
"type": "Direct",
"requested": "[1.6.0, )",

View File

@ -71,13 +71,17 @@ namespace Tests
public static Gen<ProxyForward> ProxyForward()
{
return Arb.Generate<Tuple<string, int, int, IPv4Address>>().Select(
return Arb.Generate<Tuple<Tuple<string, int, Guid, Guid, Guid?, int>, Tuple<IPv4Address, DateTimeOffset>>>().Select(
arg =>
new ProxyForward(
Region: arg.Item1,
DstPort: arg.Item2,
SrcPort: arg.Item3,
DstIp: arg.Item4.Item.ToString()
Region: arg.Item1.Item1,
Port: arg.Item1.Item2,
ScalesetId: arg.Item1.Item3,
MachineId: arg.Item1.Item4,
ProxyId: arg.Item1.Item5,
DstPort: arg.Item1.Item6,
DstIp: arg.Item2.Item1.ToString(),
EndTime: arg.Item2.Item2
)
);
}
@ -594,6 +598,8 @@ namespace Tests
}
/*
//Sample function on how repro a failing test run, using Replay
//functionality of FsCheck. Feel free to