- Move some persistent resources into SingletonResources class (#1835)

- Rename ResultOk to ResultVoid

Move environment variables to singleton

Co-authored-by: stas <statis@microsoft.com>
This commit is contained in:
Stas
2022-04-22 17:40:11 -07:00
committed by GitHub
parent 62d824383a
commit e86854cf2a
22 changed files with 192 additions and 158 deletions

View File

@ -1,65 +0,0 @@
namespace Microsoft.OneFuzz.Service;
public enum LogDestination
{
Console,
AppInsights,
}
public static class EnvironmentVariables
{
static EnvironmentVariables()
{
#if DEBUG
LogDestinations = new LogDestination[] { LogDestination.AppInsights, LogDestination.Console };
#else
LogDestinations = new LogDestination[] { LogDestination.AppInsights };
#endif
}
//TODO: Add environment variable to control where to write logs to
public static LogDestination[] LogDestinations { get; set; }
//TODO: Get this from Environment variable
public static ApplicationInsights.DataContracts.SeverityLevel LogSeverityLevel() { return ApplicationInsights.DataContracts.SeverityLevel.Verbose; }
public static class AppInsights
{
public static string? AppId { get => Environment.GetEnvironmentVariable("APPINSIGHTS_APPID"); }
public static string? InstrumentationKey { get => Environment.GetEnvironmentVariable("APPINSIGHTS_INSTRUMENTATIONKEY"); }
}
public static class AzureSignalR
{
public static string? ConnectionString { get => Environment.GetEnvironmentVariable("AzureSignalRConnectionString"); }
public static string? ServiceTransportType { get => Environment.GetEnvironmentVariable("AzureSignalRServiceTransportType"); }
}
public static class AzureWebJob
{
public static string? DisableHomePage { get => Environment.GetEnvironmentVariable("AzureWebJobsDisableHomepage"); }
public static string? Storage { get => Environment.GetEnvironmentVariable("AzureWebJobsStorage"); }
}
public static class DiagnosticsAzureBlob
{
public static string? ContainerSasUrl { get => Environment.GetEnvironmentVariable("DIAGNOSTICS_AZUREBLOBCONTAINERSASURL"); }
public static string? RetentionDays { get => Environment.GetEnvironmentVariable("DIAGNOSTICS_AZUREBLOBRETENTIONINDAYS"); }
}
public static string? MultiTenantDomain { get => Environment.GetEnvironmentVariable("MULTI_TENANT_DOMAIN"); }
public static class OneFuzz
{
public static string? DataStorage { get => Environment.GetEnvironmentVariable("ONEFUZZ_DATA_STORAGE"); }
public static string? FuncStorage { get => Environment.GetEnvironmentVariable("ONEFUZZ_FUNC_STORAGE"); }
public static string? Instance { get => Environment.GetEnvironmentVariable("ONEFUZZ_INSTANCE"); }
public static string? InstanceName { get => Environment.GetEnvironmentVariable("ONEFUZZ_INSTANCE_NAME"); }
public static string? Keyvault { get => Environment.GetEnvironmentVariable("ONEFUZZ_KEYVAULT"); }
public static string? Monitor { get => Environment.GetEnvironmentVariable("ONEFUZZ_MONITOR"); }
public static string? Owner { get => Environment.GetEnvironmentVariable("ONEFUZZ_OWNER"); }
public static string? ResourceGroup { get => Environment.GetEnvironmentVariable("ONEFUZZ_RESOURCE_GROUP"); }
public static string? Telemetry { get => Environment.GetEnvironmentVariable("ONEFUZZ_TELEMETRY"); }
}
}

View File

@ -8,22 +8,23 @@ using AccessToken = String;
public class Request
{
private static HttpClient httpClient = new HttpClient();
private readonly HttpClient _httpClient;
Func<Task<(TokenType, AccessToken)>>? auth;
Func<Task<(TokenType, AccessToken)>>? _auth;
public Request(Func<Task<(TokenType, AccessToken)>>? auth = null)
public Request(HttpClient httpClient, Func<Task<(TokenType, AccessToken)>>? auth = null)
{
this.auth = auth;
_auth = auth;
_httpClient = httpClient;
}
private async Task<HttpResponseMessage> Send(HttpMethod method, Uri url, HttpContent? content = null, IDictionary<string, string>? headers = null)
{
var request = new HttpRequestMessage(method: method, requestUri: url);
if (auth is not null)
if (_auth is not null)
{
var (tokenType, accessToken) = await auth();
var (tokenType, accessToken) = await _auth();
request.Headers.Authorization = new AuthenticationHeaderValue(tokenType, accessToken);
}
@ -40,7 +41,7 @@ public class Request
}
}
return await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
return await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
}
public async Task<HttpResponseMessage> Get(Uri url)

View File

@ -16,16 +16,19 @@ public interface ILog
class AppInsights : ILog
{
private TelemetryClient telemetryClient =
new TelemetryClient(
new TelemetryConfiguration(EnvironmentVariables.AppInsights.InstrumentationKey));
private TelemetryClient _telemetryClient;
public AppInsights(string instrumentationKey)
{
_telemetryClient = new TelemetryClient(new TelemetryConfiguration(instrumentationKey));
}
public void Log(Guid correlationId, String message, SeverityLevel level, IReadOnlyDictionary<string, string> tags, string? caller)
{
Dictionary<string, string> copyTags = new(tags);
copyTags["Correlation ID"] = correlationId.ToString();
if (caller is not null) copyTags["CalledBy"] = caller;
telemetryClient.TrackTrace(message, level, copyTags);
_telemetryClient.TrackTrace(message, level, copyTags);
}
public void LogEvent(Guid correlationId, String evt, IReadOnlyDictionary<string, string> tags, IReadOnlyDictionary<string, double>? metrics, string? caller)
{
@ -39,7 +42,7 @@ class AppInsights : ILog
copyMetrics = new(metrics);
}
telemetryClient.TrackEvent(evt, properties: copyTags, metrics: copyMetrics);
_telemetryClient.TrackEvent(evt, properties: copyTags, metrics: copyMetrics);
}
public void LogException(Guid correlationId, Exception ex, IReadOnlyDictionary<string, string> tags, IReadOnlyDictionary<string, double>? metrics, string? caller)
{
@ -52,12 +55,12 @@ class AppInsights : ILog
{
copyMetrics = new(metrics);
}
telemetryClient.TrackException(ex, copyTags, copyMetrics);
_telemetryClient.TrackException(ex, copyTags, copyMetrics);
}
public void Flush()
{
telemetryClient.Flush();
_telemetryClient.Flush();
}
}

View File

@ -352,7 +352,7 @@ public record InstanceConfig
//# At the moment, this only checks allowed_aad_tenants, however adding
//# support for 3rd party JWT validation is anticipated in a future release.
public ResultOk<List<string>> CheckInstanceConfig()
public ResultVoid<List<string>> CheckInstanceConfig()
{
List<string> errors = new();
if (AllowedAadTenants.Length == 0)
@ -361,11 +361,11 @@ public record InstanceConfig
}
if (errors.Count == 0)
{
return ResultOk<List<string>>.Ok();
return ResultVoid<List<string>>.Ok();
}
else
{
return ResultOk<List<string>>.Error(errors);
return ResultVoid<List<string>>.Error(errors);
}
}
}

View File

@ -1,17 +1,17 @@
namespace Microsoft.OneFuzz.Service
{
public struct ResultOk<T_Error>
public struct ResultVoid<T_Error>
{
public static ResultOk<T_Error> Ok() => new();
public static ResultOk<T_Error> Error(T_Error err) => new(err);
public static ResultVoid<T_Error> Ok() => new();
public static ResultVoid<T_Error> Error(T_Error err) => new(err);
readonly T_Error? error;
readonly bool isOk;
public ResultOk() => (error, isOk) = (default, true);
public ResultVoid() => (error, isOk) = (default, true);
public ResultOk(T_Error error) => (this.error, isOk) = (error, false);
public ResultVoid(T_Error error) => (this.error, isOk) = (error, false);
public bool IsOk => isOk;

View File

@ -37,15 +37,15 @@ public class Program
}
public static List<ILog> GetLoggers()
public static List<ILog> GetLoggers(IServiceConfig config)
{
List<ILog> loggers = new List<ILog>();
foreach (var dest in EnvironmentVariables.LogDestinations)
foreach (var dest in config.LogDestinations)
{
loggers.Add(
dest switch
{
LogDestination.AppInsights => new AppInsights(),
LogDestination.AppInsights => new AppInsights(config.ApplicationInsightsInstrumentationKey!),
LogDestination.Console => new Console(),
_ => throw new Exception($"Unhandled Log Destination type: {dest}"),
}
@ -66,14 +66,14 @@ public class Program
)
.ConfigureServices((context, services) =>
services
.AddScoped<ILogTracer>(s => new LogTracerFactory(GetLoggers()).CreateLogTracer(Guid.Empty, severityLevel: EnvironmentVariables.LogSeverityLevel()))
.AddScoped<ILogTracer>(s =>
new LogTracerFactory(GetLoggers(s.GetService<IServiceConfig>()!)).CreateLogTracer(Guid.Empty, severityLevel: s.GetService<IServiceConfig>()!.LogSeverityLevel))
.AddScoped<INodeOperations, NodeOperations>()
.AddScoped<IEvents, Events>()
.AddScoped<IWebhookOperations, WebhookOperations>()
.AddScoped<IWebhookMessageLogOperations, WebhookMessageLogOperations>()
.AddScoped<ITaskOperations, TaskOperations>()
.AddScoped<IQueue, Queue>()
.AddScoped<ICreds, Creds>()
.AddScoped<IStorage, Storage>()
.AddScoped<IProxyOperations, ProxyOperations>()
.AddScoped<IConfigOperations, ConfigOperations>()
@ -83,9 +83,11 @@ public class Program
.AddScoped<INotificationOperations, NotificationOperations>()
.AddScoped<IUserCredentials, UserCredentials>()
//TODO: move out expensive resources into separate class, and add those as Singleton
// ArmClient, Table Client(s), Queue Client(s), HttpClient, etc.
//Move out expensive resources into separate class, and add those as Singleton
// ArmClient, Table Client(s), Queue Client(s), HttpClient, etc.
.AddSingleton<ICreds, Creds>()
.AddSingleton<IServiceConfig, ServiceConfiguration>()
)
.Build();

View File

@ -0,0 +1,82 @@
namespace Microsoft.OneFuzz.Service;
public enum LogDestination
{
Console,
AppInsights,
}
public interface IServiceConfig
{
public LogDestination[] LogDestinations { get; set; }
public ApplicationInsights.DataContracts.SeverityLevel LogSeverityLevel { get; }
public string? ApplicationInsightsAppId { get; }
public string? ApplicationInsightsInstrumentationKey { get; }
public string? AzureSignalRConnectionString { get; }
public string? AzureSignalRServiceTransportType { get; }
public string? AzureWebJobDisableHomePage { get; }
public string? AzureWebJobStorage { get; }
public string? DiagnosticsAzureBlobContainerSasUrl { get; }
public string? DiagnosticsAzureBlobRetentionDays { get; }
public string? MultiTenantDomain { get; }
public string? OneFuzzDataStorage { get; }
public string? OneFuzzFuncStorage { get; }
public string? OneFuzzInstance { get; }
public string? OneFuzzInstanceName { get; }
public string? OneFuzzKeyvault { get; }
public string? OneFuzzMonitor { get; }
public string? OneFuzzOwner { get; }
public string? OneFuzzResourceGroup { get; }
public string? OneFuzzTelemetry { get; }
}
public class ServiceConfiguration : IServiceConfig
{
public ServiceConfiguration()
{
#if DEBUG
LogDestinations = new LogDestination[] { LogDestination.AppInsights, LogDestination.Console };
#else
LogDestinations = new LogDestination[] { LogDestination.AppInsights };
#endif
}
//TODO: Add environment variable to control where to write logs to
public LogDestination[] LogDestinations { get; set; }
//TODO: Get this from Environment variable
public ApplicationInsights.DataContracts.SeverityLevel LogSeverityLevel => ApplicationInsights.DataContracts.SeverityLevel.Verbose;
public string? ApplicationInsightsAppId => Environment.GetEnvironmentVariable("APPINSIGHTS_APPID");
public string? ApplicationInsightsInstrumentationKey => Environment.GetEnvironmentVariable("APPINSIGHTS_INSTRUMENTATIONKEY");
public string? AzureSignalRConnectionString => Environment.GetEnvironmentVariable("AzureSignalRConnectionString");
public string? AzureSignalRServiceTransportType => Environment.GetEnvironmentVariable("AzureSignalRServiceTransportType");
public string? AzureWebJobDisableHomePage { get => Environment.GetEnvironmentVariable("AzureWebJobsDisableHomepage"); }
public string? AzureWebJobStorage { get => Environment.GetEnvironmentVariable("AzureWebJobsStorage"); }
public string? DiagnosticsAzureBlobContainerSasUrl { get => Environment.GetEnvironmentVariable("DIAGNOSTICS_AZUREBLOBCONTAINERSASURL"); }
public string? DiagnosticsAzureBlobRetentionDays { get => Environment.GetEnvironmentVariable("DIAGNOSTICS_AZUREBLOBRETENTIONINDAYS"); }
public string? MultiTenantDomain { get => Environment.GetEnvironmentVariable("MULTI_TENANT_DOMAIN"); }
public string? OneFuzzDataStorage { get => Environment.GetEnvironmentVariable("ONEFUZZ_DATA_STORAGE"); }
public string? OneFuzzFuncStorage { get => Environment.GetEnvironmentVariable("ONEFUZZ_FUNC_STORAGE"); }
public string? OneFuzzInstance { get => Environment.GetEnvironmentVariable("ONEFUZZ_INSTANCE"); }
public string? OneFuzzInstanceName { get => Environment.GetEnvironmentVariable("ONEFUZZ_INSTANCE_NAME"); }
public string? OneFuzzKeyvault { get => Environment.GetEnvironmentVariable("ONEFUZZ_KEYVAULT"); }
public string? OneFuzzMonitor { get => Environment.GetEnvironmentVariable("ONEFUZZ_MONITOR"); }
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"); }
}

View File

@ -7,20 +7,20 @@ using Microsoft.OneFuzz.Service.OneFuzzLib.Orm;
namespace Microsoft.OneFuzz.Service;
public record FunctionInfo(string Name, string ResourceGroup, string? SlotName);
public class TestHooks
{
private readonly ILogTracer _log;
private readonly IConfigOperations _configOps;
private readonly IEvents _events;
private readonly IServiceConfig _config;
public TestHooks(ILogTracer log, IConfigOperations configOps, IEvents events)
public TestHooks(ILogTracer log, IConfigOperations configOps, IEvents events, IServiceConfig config)
{
_log = log;
_configOps = configOps;
_events = events;
_config = config;
}
[Function("Info")]
@ -29,8 +29,8 @@ public class TestHooks
_log.Info("Creating function info response");
var response = req.CreateResponse();
FunctionInfo info = new(
$"{EnvironmentVariables.OneFuzz.InstanceName}",
$"{EnvironmentVariables.OneFuzz.ResourceGroup}",
$"{_config.OneFuzzInstanceName}",
$"{_config.OneFuzzResourceGroup}",
Environment.GetEnvironmentVariable("WEBSITE_SLOT_NAME"));
_log.Info("Returning function info");

View File

@ -27,7 +27,7 @@ public class Containers : IContainers
_log = log;
_storage = storage;
_creds = creds;
_armClient = new ArmClient(credential: _creds.GetIdentity(), defaultSubscriptionId: _creds.GetSubcription());
_armClient = creds.ArmClient;
}
public async Task<IEnumerable<byte>?> GetBlob(Container container, string name, StorageType storageType)
{

View File

@ -23,26 +23,27 @@ public interface ICreds
public class Creds : ICreds
{
private readonly Lazy<ArmClient> _armClient;
private readonly ArmClient _armClient;
private readonly DefaultAzureCredential _azureCredential;
private readonly IServiceConfig _config;
public ArmClient ArmClient => _armClient.Value;
public ArmClient ArmClient => _armClient;
public Creds()
public Creds(IServiceConfig config)
{
_armClient = new Lazy<ArmClient>(() => new ArmClient(this.GetIdentity(), this.GetSubcription()), true);
_armClient = new ArmClient(this.GetIdentity(), this.GetSubcription());
_azureCredential = new DefaultAzureCredential();
_config = config;
}
// TODO: @cached
public DefaultAzureCredential GetIdentity()
{
// TODO: AllowMoreWorkers
// TODO: ReduceLogging
return new DefaultAzureCredential();
return _azureCredential;
}
public string GetSubcription()
{
var storageResourceId = EnvironmentVariables.OneFuzz.DataStorage
var storageResourceId = _config.OneFuzzDataStorage
?? throw new System.Exception("Data storage env var is not present");
var storageResource = new ResourceIdentifier(storageResourceId);
return storageResource.SubscriptionId!;
@ -50,7 +51,7 @@ public class Creds : ICreds
public string GetBaseResourceGroup()
{
var storageResourceId = EnvironmentVariables.OneFuzz.DataStorage
var storageResourceId = _config.OneFuzzDataStorage
?? throw new System.Exception("Data storage env var is not present");
var storageResource = new ResourceIdentifier(storageResourceId);
return storageResource.ResourceGroupName!;
@ -58,7 +59,7 @@ public class Creds : ICreds
public ResourceIdentifier GetResourceGroupResourceIdentifier()
{
var resourceId = EnvironmentVariables.OneFuzz.ResourceGroup
var resourceId = _config.OneFuzzResourceGroup
?? throw new System.Exception("Resource group env var is not present");
return new ResourceIdentifier(resourceId);
}

View File

@ -72,6 +72,8 @@ namespace Microsoft.OneFuzz.Service
{
public override UserInfo? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
//TODO: I might be wrong but seems like better way of doing this is to have a separate type,
//that if object of the type - then ignore user info...
var newOptions = new JsonSerializerOptions(options);
RemoveUserInfo? self = null;
foreach (var converter in newOptions.Converters)

View File

@ -15,22 +15,24 @@ public class ConfigOperations : Orm<InstanceConfig>, IConfigOperations
{
private readonly IEvents _events;
private readonly ILogTracer _log;
public ConfigOperations(IStorage storage, IEvents events, ILogTracer log) : base(storage, 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()
{
var key = EnvironmentVariables.OneFuzz.InstanceName ?? throw new Exception("Environment variable ONEFUZZ_INSTANCE_NAME is not set");
var key = _config.OneFuzzInstanceName ?? throw new Exception("Environment variable ONEFUZZ_INSTANCE_NAME is not set");
var config = await GetEntityAsync(key, key);
return config;
}
public async Async.Task Save(InstanceConfig config, bool isNew = false, bool requireEtag = false)
{
ResultOk<(int, string)> r;
ResultVoid<(int, string)> r;
if (isNew)
{
r = await Insert(config);

View File

@ -11,8 +11,8 @@ public interface INodeOperations : IStatefulOrm<Node, NodeState>
public class NodeOperations : StatefulOrm<Node, NodeState>, INodeOperations
{
public NodeOperations(IStorage storage, ILogTracer log)
: base(storage, log)
public NodeOperations(IStorage storage, ILogTracer log, IServiceConfig config)
: base(storage, log, config)
{
}

View File

@ -20,8 +20,8 @@ public class NotificationOperations : Orm<Notification>, INotificationOperations
private IEvents _events;
public NotificationOperations(ILogTracer log, IStorage storage, IReports reports, ITaskOperations taskOperations, IContainers containers, IQueue queue, IEvents events)
: base(storage, log)
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;

View File

@ -19,8 +19,8 @@ public class ProxyOperations : StatefulOrm<Proxy, VmState>, IProxyOperations
private readonly IEvents _events;
public ProxyOperations(ILogTracer log, IStorage storage, IEvents events)
: base(storage, log)
public ProxyOperations(ILogTracer log, IStorage storage, IEvents events, IServiceConfig config)
: base(storage, log, config)
{
_log = log;
_events = events;

View File

@ -10,8 +10,8 @@ public interface IScalesetOperations : IOrm<Scaleset>
public class ScalesetOperations : StatefulOrm<Scaleset, ScalesetState>, IScalesetOperations
{
public ScalesetOperations(IStorage storage, ILogTracer log)
: base(storage, log)
public ScalesetOperations(IStorage storage, ILogTracer log, IServiceConfig config)
: base(storage, log, config)
{
}

View File

@ -25,23 +25,25 @@ public class Storage : IStorage
private ICreds _creds;
private ArmClient _armClient;
private ILogTracer _log;
private IServiceConfig _config;
public Storage(ICreds creds, ILogTracer log)
public Storage(ICreds creds, ILogTracer log, IServiceConfig config)
{
_creds = creds;
_armClient = new ArmClient(credential: _creds.GetIdentity(), defaultSubscriptionId: _creds.GetSubcription());
_armClient = creds.ArmClient;
_log = log;
_config = config;
}
public static string GetFuncStorage()
public string GetFuncStorage()
{
return EnvironmentVariables.OneFuzz.FuncStorage
return _config.OneFuzzFuncStorage
?? throw new Exception("Func storage env var is missing");
}
public static string GetFuzzStorage()
public string GetFuzzStorage()
{
return EnvironmentVariables.OneFuzz.DataStorage
return _config.OneFuzzDataStorage
?? throw new Exception("Fuzz storage env var is missing");
}

View File

@ -1,9 +1,6 @@
using Azure.Core;
using Azure.ResourceManager.Network;
using Azure.ResourceManager.Network;
namespace Microsoft.OneFuzz.Service;
public interface ISubnet
{
System.Threading.Tasks.Task<VirtualNetworkResource?> GetVnet(string vnetName);
@ -35,7 +32,7 @@ public partial class TimerProxy
public async System.Threading.Tasks.Task<VirtualNetworkResource?> GetVnet(string vnetName)
{
var resourceGroupId = new ResourceIdentifier(EnvironmentVariables.OneFuzz.ResourceGroup ?? throw new Exception("Missing resource group"));
var resourceGroupId = _creds.GetResourceGroupResourceIdentifier();
var response = await _creds.ArmClient.GetResourceGroupResource(resourceGroupId).GetVirtualNetworkAsync(vnetName);
return response.Value;
}

View File

@ -18,8 +18,8 @@ public interface ITaskOperations : IStatefulOrm<Task, TaskState>
public class TaskOperations : StatefulOrm<Task, TaskState>, ITaskOperations
{
public TaskOperations(IStorage storage, ILogTracer log)
: base(storage, log)
public TaskOperations(IStorage storage, ILogTracer log, IServiceConfig config)
: base(storage, log, config)
{
}

View File

@ -20,7 +20,7 @@ public class WebhookMessageLogOperations : Orm<WebhookMessageLog>, IWebhookMessa
private readonly IQueue _queue;
private readonly ILogTracer _log;
public WebhookMessageLogOperations(IStorage storage, IQueue queue, ILogTracer log) : base(storage, log)
public WebhookMessageLogOperations(IStorage storage, IQueue queue, ILogTracer log, IServiceConfig config) : base(storage, log, config)
{
_queue = queue;
_log = log;
@ -77,8 +77,8 @@ public class WebhookOperations : Orm<Webhook>, IWebhookOperations
{
private readonly IWebhookMessageLogOperations _webhookMessageLogOperations;
private readonly ILogTracer _log;
public WebhookOperations(IStorage storage, IWebhookMessageLogOperations webhookMessageLogOperations, ILogTracer log)
: base(storage, log)
public WebhookOperations(IStorage storage, IWebhookMessageLogOperations webhookMessageLogOperations, ILogTracer log, IServiceConfig config)
: base(storage, log, config)
{
_webhookMessageLogOperations = webhookMessageLogOperations;
_log = log;

View File

@ -11,11 +11,11 @@ namespace ApiService.OneFuzzLib.Orm
{
Task<TableClient> GetTableClient(string table, string? accountId = null);
IAsyncEnumerable<T> QueryAsync(string? filter = null);
Task<ResultOk<(int, string)>> Replace(T entity);
Task<ResultVoid<(int, string)>> Replace(T entity);
Task<T> GetEntityAsync(string partitionKey, string rowKey);
Task<ResultOk<(int, string)>> Insert(T entity);
Task<ResultOk<(int, string)>> Delete(T entity);
Task<ResultVoid<(int, string)>> Insert(T entity);
Task<ResultVoid<(int, string)>> Delete(T entity);
}
@ -26,15 +26,16 @@ namespace ApiService.OneFuzzLib.Orm
{
IStorage _storage;
EntityConverter _entityConverter;
IServiceConfig _config;
protected ILogTracer _logTracer;
public Orm(IStorage storage, ILogTracer logTracer)
public Orm(IStorage storage, ILogTracer logTracer, IServiceConfig config)
{
_storage = storage;
_entityConverter = new EntityConverter();
_logTracer = logTracer;
_config = config;
}
public async IAsyncEnumerable<T> QueryAsync(string? filter = null)
@ -47,7 +48,7 @@ namespace ApiService.OneFuzzLib.Orm
}
}
public async Task<ResultOk<(int, string)>> Insert(T entity)
public async Task<ResultVoid<(int, string)>> Insert(T entity)
{
var tableClient = await GetTableClient(typeof(T).Name);
var tableEntity = _entityConverter.ToTableEntity(entity);
@ -55,48 +56,48 @@ namespace ApiService.OneFuzzLib.Orm
if (response.IsError)
{
return ResultOk<(int, string)>.Error((response.Status, response.ReasonPhrase));
return ResultVoid<(int, string)>.Error((response.Status, response.ReasonPhrase));
}
else
{
return ResultOk<(int, string)>.Ok();
return ResultVoid<(int, string)>.Ok();
}
}
public async Task<ResultOk<(int, string)>> Replace(T entity)
public async Task<ResultVoid<(int, string)>> Replace(T entity)
{
var tableClient = await GetTableClient(typeof(T).Name);
var tableEntity = _entityConverter.ToTableEntity(entity);
var response = await tableClient.UpsertEntityAsync(tableEntity);
if (response.IsError)
{
return ResultOk<(int, string)>.Error((response.Status, response.ReasonPhrase));
return ResultVoid<(int, string)>.Error((response.Status, response.ReasonPhrase));
}
else
{
return ResultOk<(int, string)>.Ok();
return ResultVoid<(int, string)>.Ok();
}
}
public async Task<ResultOk<(int, string)>> Update(T entity)
public async Task<ResultVoid<(int, string)>> Update(T entity)
{
var tableClient = await GetTableClient(typeof(T).Name);
var tableEntity = _entityConverter.ToTableEntity(entity);
if (entity.ETag is null)
{
return ResultOk<(int, string)>.Error((0, "ETag must be set when updating an entity"));
return ResultVoid<(int, string)>.Error((0, "ETag must be set when updating an entity"));
}
else
{
var response = await tableClient.UpdateEntityAsync(tableEntity, entity.ETag.Value);
if (response.IsError)
{
return ResultOk<(int, string)>.Error((response.Status, response.ReasonPhrase));
return ResultVoid<(int, string)>.Error((response.Status, response.ReasonPhrase));
}
else
{
return ResultOk<(int, string)>.Ok();
return ResultVoid<(int, string)>.Ok();
}
}
}
@ -110,25 +111,25 @@ namespace ApiService.OneFuzzLib.Orm
public async Task<TableClient> GetTableClient(string table, string? accountId = null)
{
var account = accountId ?? EnvironmentVariables.OneFuzz.FuncStorage ?? throw new ArgumentNullException(nameof(accountId));
var account = accountId ?? _config.OneFuzzFuncStorage ?? throw new ArgumentNullException(nameof(accountId));
var (name, key) = _storage.GetStorageAccountNameAndKey(account);
var tableClient = new TableServiceClient(new Uri($"https://{name}.table.core.windows.net"), new TableSharedKeyCredential(name, key));
await tableClient.CreateTableIfNotExistsAsync(table);
return tableClient.GetTableClient(table);
}
public async Task<ResultOk<(int, string)>> Delete(T entity)
public async Task<ResultVoid<(int, string)>> Delete(T entity)
{
var tableClient = await GetTableClient(typeof(T).Name);
var tableEntity = _entityConverter.ToTableEntity(entity);
var response = await tableClient.DeleteEntityAsync(tableEntity.PartitionKey, tableEntity.RowKey);
if (response.IsError)
{
return ResultOk<(int, string)>.Error((response.Status, response.ReasonPhrase));
return ResultVoid<(int, string)>.Error((response.Status, response.ReasonPhrase));
}
else
{
return ResultOk<(int, string)>.Ok();
return ResultVoid<(int, string)>.Ok();
}
}
}
@ -166,7 +167,7 @@ namespace ApiService.OneFuzzLib.Orm
};
}
public StatefulOrm(IStorage storage, ILogTracer logTracer) : base(storage, logTracer)
public StatefulOrm(IStorage storage, ILogTracer logTracer, IServiceConfig config) : base(storage, logTracer, config)
{
}

View File

@ -837,6 +837,12 @@ namespace Tests
}
[Property]
public bool RegressionReportOrReport(RegressionReportOrReport e)
{
return Test(e);
}
/*
//Sample function on how repro a failing test run, using Replay
//functionality of FsCheck. Feel free to