577 lines
19 KiB
C#
577 lines
19 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Net.Http;
|
|
using System.Threading.Tasks;
|
|
|
|
using Frostfs.V2.Ape;
|
|
using Frostfs.V2.Apemanager;
|
|
|
|
using FrostFS.Accounting;
|
|
using FrostFS.Container;
|
|
using FrostFS.Netmap;
|
|
using FrostFS.Object;
|
|
using FrostFS.SDK.ClientV2.Interfaces;
|
|
using FrostFS.SDK.ClientV2.Services;
|
|
using FrostFS.SDK.Cryptography;
|
|
using FrostFS.Session;
|
|
|
|
using Grpc.Core;
|
|
using Grpc.Core.Interceptors;
|
|
using Grpc.Net.Client;
|
|
|
|
using Microsoft.Extensions.Options;
|
|
|
|
namespace FrostFS.SDK.ClientV2;
|
|
|
|
public class FrostFSClient : IFrostFSClient
|
|
{
|
|
private bool isDisposed;
|
|
|
|
internal ContainerService.ContainerServiceClient? ContainerServiceClient { get; set; }
|
|
|
|
internal NetmapService.NetmapServiceClient? NetmapServiceClient { get; set; }
|
|
|
|
internal APEManagerService.APEManagerServiceClient? ApeManagerServiceClient { get; set; }
|
|
|
|
internal SessionService.SessionServiceClient? SessionServiceClient { get; set; }
|
|
|
|
internal ObjectService.ObjectServiceClient? ObjectServiceClient { get; set; }
|
|
|
|
internal AccountingService.AccountingServiceClient? AccountingServiceClient { get; set; }
|
|
|
|
internal ClientContext ClientCtx { get; set; }
|
|
|
|
public static IFrostFSClient GetInstance(IOptions<ClientSettings> clientOptions, GrpcChannelOptions? channelOptions = null)
|
|
{
|
|
return new FrostFSClient(clientOptions, channelOptions);
|
|
}
|
|
|
|
public static IFrostFSClient GetSingleOwnerInstance(IOptions<SingleOwnerClientSettings> clientOptions, GrpcChannelOptions? channelOptions = null)
|
|
{
|
|
return new FrostFSClient(clientOptions, channelOptions);
|
|
}
|
|
|
|
/// <summary>
|
|
/// For test only. Provide custom implementation or mock object to inject required logic instead of internal gRPC client.
|
|
/// </summary>
|
|
/// <param name="clientOptions">Global setting for client</param>
|
|
/// <param name="channelOptions">Setting for gRPC channel</param>
|
|
/// <param name="containerService">ContainerService.ContainerServiceClient implementation</param>
|
|
/// <param name="netmapService">Netmap.NetmapService.NetmapServiceClient implementation</param>
|
|
/// <param name="sessionService">Session.SessionService.SessionServiceClient implementation</param>
|
|
/// <param name="objectService">Object.ObjectService.ObjectServiceClient implementation</param>
|
|
/// <returns></returns>
|
|
public static IFrostFSClient GetTestInstance(
|
|
IOptions<SingleOwnerClientSettings> clientOptions,
|
|
GrpcChannelOptions? channelOptions,
|
|
NetmapService.NetmapServiceClient netmapService,
|
|
SessionService.SessionServiceClient sessionService,
|
|
ContainerService.ContainerServiceClient containerService,
|
|
ObjectService.ObjectServiceClient objectService)
|
|
{
|
|
if (clientOptions is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(clientOptions));
|
|
}
|
|
|
|
return new FrostFSClient(clientOptions, channelOptions, containerService, netmapService, sessionService, objectService);
|
|
}
|
|
|
|
private FrostFSClient(
|
|
IOptions<SingleOwnerClientSettings> settings,
|
|
GrpcChannelOptions? channelOptions,
|
|
ContainerService.ContainerServiceClient containerService,
|
|
NetmapService.NetmapServiceClient netmapService,
|
|
SessionService.SessionServiceClient sessionService,
|
|
ObjectService.ObjectServiceClient objectService)
|
|
{
|
|
if (settings is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(settings));
|
|
}
|
|
|
|
var ecdsaKey = settings.Value.Key.LoadWif();
|
|
FrostFsOwner.FromKey(ecdsaKey);
|
|
|
|
ClientCtx = new ClientContext(
|
|
client: this,
|
|
key: ecdsaKey,
|
|
owner: FrostFsOwner.FromKey(ecdsaKey),
|
|
channel: InitGrpcChannel(settings.Value.Host, channelOptions),
|
|
version: new FrostFsVersion(2, 13));
|
|
|
|
ContainerServiceClient = containerService ?? throw new ArgumentNullException(nameof(containerService));
|
|
NetmapServiceClient = netmapService ?? throw new ArgumentNullException(nameof(netmapService));
|
|
SessionServiceClient = sessionService ?? throw new ArgumentNullException(nameof(sessionService));
|
|
ObjectServiceClient = objectService ?? throw new ArgumentNullException(nameof(objectService));
|
|
}
|
|
|
|
private FrostFSClient(IOptions<ClientSettings> options, GrpcChannelOptions? channelOptions)
|
|
{
|
|
var clientSettings = (options?.Value) ?? throw new ArgumentNullException(nameof(options), "Options value must be initialized");
|
|
|
|
clientSettings.Validate();
|
|
|
|
var channel = InitGrpcChannel(clientSettings.Host, channelOptions);
|
|
|
|
ClientCtx = new ClientContext(
|
|
this,
|
|
key: null,
|
|
owner: null,
|
|
channel: channel,
|
|
version: new FrostFsVersion(2, 13));
|
|
|
|
// TODO: define timeout logic
|
|
// CheckFrostFsVersionSupport(new Context { Timeout = TimeSpan.FromSeconds(20) });
|
|
}
|
|
|
|
private FrostFSClient(IOptions<SingleOwnerClientSettings> options, GrpcChannelOptions? channelOptions)
|
|
{
|
|
var clientSettings = (options?.Value) ?? throw new ArgumentNullException(nameof(options), "Options value must be initialized");
|
|
|
|
clientSettings.Validate();
|
|
|
|
var ecdsaKey = clientSettings.Key.LoadWif();
|
|
|
|
var channel = InitGrpcChannel(clientSettings.Host, channelOptions);
|
|
|
|
ClientCtx = new ClientContext(
|
|
this,
|
|
key: ecdsaKey,
|
|
owner: FrostFsOwner.FromKey(ecdsaKey),
|
|
channel: channel,
|
|
version: new FrostFsVersion(2, 13));
|
|
|
|
// TODO: define timeout logic
|
|
// CheckFrostFsVersionSupport(new Context { Timeout = TimeSpan.FromSeconds(20) });
|
|
}
|
|
|
|
internal FrostFSClient(WrapperPrm prm, SessionCache cache)
|
|
{
|
|
ClientCtx = new ClientContext(
|
|
client: this,
|
|
key: prm.Key,
|
|
owner: FrostFsOwner.FromKey(prm.Key!),
|
|
channel: InitGrpcChannel(prm.Address, null), //prm.GrpcChannelOptions),
|
|
version: new FrostFsVersion(2, 13))
|
|
{
|
|
SessionCache = cache
|
|
};
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
Dispose(true);
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
|
|
protected virtual void Dispose(bool disposing)
|
|
{
|
|
if (disposing && !isDisposed)
|
|
{
|
|
ClientCtx?.Dispose();
|
|
isDisposed = true;
|
|
}
|
|
}
|
|
|
|
#region ApeManagerImplementation
|
|
public Task<byte[]> AddChainAsync(PrmApeChainAdd args)
|
|
{
|
|
if (args is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(args));
|
|
}
|
|
|
|
var service = GetApeManagerService(args);
|
|
return service.AddChainAsync(args);
|
|
}
|
|
|
|
public Task RemoveChainAsync(PrmApeChainRemove args)
|
|
{
|
|
if (args is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(args));
|
|
}
|
|
|
|
var service = GetApeManagerService(args);
|
|
return service.RemoveChainAsync(args);
|
|
}
|
|
|
|
public Task<Chain[]> ListChainAsync(PrmApeChainList args)
|
|
{
|
|
if (args is null)
|
|
throw new ArgumentNullException(nameof(args));
|
|
|
|
var service = GetApeManagerService(args);
|
|
return service.ListChainAsync(args);
|
|
}
|
|
#endregion
|
|
|
|
#region ContainerImplementation
|
|
public Task<FrostFsContainerInfo> GetContainerAsync(PrmContainerGet args)
|
|
{
|
|
if (args is null)
|
|
throw new ArgumentNullException(nameof(args));
|
|
|
|
var service = GetContainerService(args);
|
|
return service.GetContainerAsync(args);
|
|
}
|
|
|
|
public IAsyncEnumerable<FrostFsContainerId> ListContainersAsync(PrmContainerGetAll? args = null)
|
|
{
|
|
args ??= new PrmContainerGetAll();
|
|
var service = GetContainerService(args);
|
|
return service.ListContainersAsync(args);
|
|
}
|
|
|
|
public Task<FrostFsContainerId> CreateContainerAsync(PrmContainerCreate args)
|
|
{
|
|
if (args is null)
|
|
throw new ArgumentNullException(nameof(args));
|
|
|
|
var service = GetContainerService(args);
|
|
return service.CreateContainerAsync(args);
|
|
}
|
|
|
|
public Task DeleteContainerAsync(PrmContainerDelete args)
|
|
{
|
|
if (args is null)
|
|
throw new ArgumentNullException(nameof(args));
|
|
|
|
var service = GetContainerService(args);
|
|
return service.DeleteContainerAsync(args);
|
|
}
|
|
#endregion
|
|
|
|
#region NetworkImplementation
|
|
public Task<FrostFsNetmapSnapshot> GetNetmapSnapshotAsync(PrmNetmapSnapshot? args)
|
|
{
|
|
args ??= new PrmNetmapSnapshot();
|
|
var service = GetNetmapService(args);
|
|
return service.GetNetmapSnapshotAsync(args);
|
|
}
|
|
|
|
public Task<FrostFsNodeInfo> GetNodeInfoAsync(PrmNodeInfo? args)
|
|
{
|
|
args ??= new PrmNodeInfo();
|
|
var service = GetNetmapService(args);
|
|
return service.GetLocalNodeInfoAsync(args);
|
|
}
|
|
|
|
public Task<NetworkSettings> GetNetworkSettingsAsync(PrmNetworkSettings? args)
|
|
{
|
|
args ??= new PrmNetworkSettings();
|
|
var service = GetNetmapService(args);
|
|
return service.GetNetworkSettingsAsync(args.Context!);
|
|
}
|
|
#endregion
|
|
|
|
#region ObjectImplementation
|
|
public Task<FrostFsObjectHeader> GetObjectHeadAsync(PrmObjectHeadGet args)
|
|
{
|
|
if (args is null)
|
|
throw new ArgumentNullException(nameof(args));
|
|
|
|
var service = GetObjectService(args);
|
|
return service.GetObjectHeadAsync(args);
|
|
}
|
|
|
|
public Task<FrostFsObject> GetObjectAsync(PrmObjectGet args)
|
|
{
|
|
if (args is null)
|
|
throw new ArgumentNullException(nameof(args));
|
|
|
|
var service = GetObjectService(args);
|
|
return service.GetObjectAsync(args);
|
|
}
|
|
|
|
public Task<RangeReader> GetRangeAsync(PrmRangeGet args)
|
|
{
|
|
if (args is null)
|
|
throw new ArgumentNullException(nameof(args));
|
|
|
|
var service = GetObjectService(args);
|
|
return service.GetRangeAsync(args);
|
|
}
|
|
|
|
public Task<IEnumerable<ReadOnlyMemory<byte>>> GetRangeHashAsync(PrmRangeHashGet args)
|
|
{
|
|
if (args is null)
|
|
throw new ArgumentNullException(nameof(args));
|
|
|
|
var service = GetObjectService(args);
|
|
return service.GetRangeHashAsync(args);
|
|
}
|
|
|
|
|
|
public Task<FrostFsObjectId> PutObjectAsync(PrmObjectPut args)
|
|
{
|
|
if (args is null)
|
|
throw new ArgumentNullException(nameof(args));
|
|
|
|
var service = GetObjectService(args);
|
|
return service.PutObjectAsync(args);
|
|
}
|
|
|
|
public Task<FrostFsObjectId> PutSingleObjectAsync(PrmSingleObjectPut args)
|
|
{
|
|
if (args is null)
|
|
throw new ArgumentNullException(nameof(args));
|
|
|
|
var service = GetObjectService(args);
|
|
return service.PutSingleObjectAsync(args);
|
|
}
|
|
|
|
public Task<FrostFsObjectId> PatchObjectAsync(PrmObjectPatch args)
|
|
{
|
|
if (args is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(args));
|
|
}
|
|
|
|
var service = GetObjectService(args);
|
|
return service.PatchObjectAsync(args);
|
|
}
|
|
|
|
public Task DeleteObjectAsync(PrmObjectDelete args)
|
|
{
|
|
if (args is null)
|
|
throw new ArgumentNullException(nameof(args));
|
|
|
|
var service = GetObjectService(args);
|
|
return service.DeleteObjectAsync(args);
|
|
}
|
|
|
|
public IAsyncEnumerable<FrostFsObjectId> SearchObjectsAsync(PrmObjectSearch args)
|
|
{
|
|
if (args is null)
|
|
throw new ArgumentNullException(nameof(args));
|
|
|
|
var service = GetObjectService(args);
|
|
return service.SearchObjectsAsync(args);
|
|
}
|
|
#endregion
|
|
|
|
#region Session Implementation
|
|
public async Task<FrostFsSessionToken> CreateSessionAsync(PrmSessionCreate args)
|
|
{
|
|
if (args is null)
|
|
throw new ArgumentNullException(nameof(args));
|
|
|
|
var session = await CreateSessionInternalAsync(args).ConfigureAwait(false);
|
|
|
|
var token = session.Serialize();
|
|
return new FrostFsSessionToken(token, session.Body.Id.ToUuid());
|
|
}
|
|
|
|
internal Task<SessionToken> CreateSessionInternalAsync(PrmSessionCreate args)
|
|
{
|
|
if (args is null)
|
|
throw new ArgumentNullException(nameof(args));
|
|
|
|
var service = GetSessionService(args);
|
|
return service.CreateSessionAsync(args);
|
|
}
|
|
#endregion
|
|
|
|
#region Accounting Implementation
|
|
public async Task<Accounting.Decimal> GetBalanceAsync(PrmBalance? args)
|
|
{
|
|
args ??= new PrmBalance();
|
|
|
|
var service = GetAccouningService(args);
|
|
return await service.GetBallance(args).ConfigureAwait(false);
|
|
}
|
|
#endregion
|
|
|
|
#region ToolsImplementation
|
|
public FrostFsObjectId CalculateObjectId(FrostFsObjectHeader header, CallContext ctx)
|
|
{
|
|
if (header == null)
|
|
throw new ArgumentNullException(nameof(header));
|
|
|
|
return ObjectTools.CalculateObjectId(header, ctx);
|
|
}
|
|
#endregion
|
|
|
|
private async void CheckFrostFsVersionSupport(CallContext? ctx = default)
|
|
{
|
|
var args = new PrmNodeInfo(ctx);
|
|
|
|
if (ctx?.Version == null)
|
|
throw new ArgumentNullException(nameof(ctx), "Version must be initialized");
|
|
|
|
var service = GetNetmapService(args);
|
|
var localNodeInfo = await service.GetLocalNodeInfoAsync(args).ConfigureAwait(false);
|
|
|
|
if (!localNodeInfo.Version.IsSupported(ctx.Version))
|
|
{
|
|
var msg = $"FrostFS {localNodeInfo.Version} is not supported.";
|
|
throw new FrostFsException(msg);
|
|
}
|
|
}
|
|
|
|
private CallInvoker? SetupClientContext(IContext ctx)
|
|
{
|
|
if (isDisposed)
|
|
throw new FrostFsInvalidObjectException("Client is disposed.");
|
|
|
|
if (ctx.Context!.Key == null)
|
|
{
|
|
if (ClientCtx.Key == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(ctx), "Key is not initialized.");
|
|
}
|
|
|
|
ctx.Context.Key = ClientCtx.Key.ECDsaKey;
|
|
}
|
|
|
|
if (ctx.Context.OwnerId == null)
|
|
{
|
|
ctx.Context.OwnerId = ClientCtx.Owner ?? FrostFsOwner.FromKey(ctx.Context.Key);
|
|
}
|
|
|
|
if (ctx.Context.Version == null)
|
|
{
|
|
if (ClientCtx.Version == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(ctx), "Version is not initialized.");
|
|
}
|
|
|
|
ctx.Context.Version = ClientCtx.Version;
|
|
}
|
|
|
|
CallInvoker? callInvoker = null;
|
|
|
|
foreach (var interceptor in ctx.Context.Interceptors)
|
|
callInvoker = AddInvoker(callInvoker, interceptor);
|
|
|
|
if (ctx.Context.Callback != null)
|
|
callInvoker = AddInvoker(callInvoker, new MetricsInterceptor(ctx.Context.Callback));
|
|
|
|
if (ctx.Context.PoolErrorHandler != null)
|
|
callInvoker = AddInvoker(callInvoker, new ErrorInterceptor(ctx.Context.PoolErrorHandler));
|
|
|
|
return callInvoker;
|
|
|
|
CallInvoker AddInvoker(CallInvoker? callInvoker, Interceptor interceptor)
|
|
{
|
|
if (callInvoker == null)
|
|
callInvoker = ClientCtx.Channel.Intercept(interceptor);
|
|
else
|
|
callInvoker = callInvoker.Intercept(interceptor);
|
|
|
|
return callInvoker;
|
|
}
|
|
}
|
|
|
|
private NetmapServiceProvider GetNetmapService(IContext ctx)
|
|
{
|
|
var callInvoker = SetupClientContext(ctx);
|
|
var client = NetmapServiceClient ?? (callInvoker != null
|
|
? new NetmapService.NetmapServiceClient(callInvoker)
|
|
: new NetmapService.NetmapServiceClient(ClientCtx.Channel));
|
|
|
|
return new NetmapServiceProvider(client, ClientCtx);
|
|
}
|
|
|
|
private SessionServiceProvider GetSessionService(IContext ctx)
|
|
{
|
|
var callInvoker = SetupClientContext(ctx);
|
|
var client = SessionServiceClient ?? (callInvoker != null
|
|
? new SessionService.SessionServiceClient(callInvoker)
|
|
: new SessionService.SessionServiceClient(ClientCtx.Channel));
|
|
|
|
return new SessionServiceProvider(client, ClientCtx);
|
|
}
|
|
|
|
private ApeManagerServiceProvider GetApeManagerService(IContext ctx)
|
|
{
|
|
var callInvoker = SetupClientContext(ctx);
|
|
var client = ApeManagerServiceClient ?? (callInvoker != null
|
|
? new APEManagerService.APEManagerServiceClient(callInvoker)
|
|
: new APEManagerService.APEManagerServiceClient(ClientCtx.Channel));
|
|
|
|
return new ApeManagerServiceProvider(client, ClientCtx);
|
|
}
|
|
|
|
private AccountingServiceProvider GetAccouningService(IContext ctx)
|
|
{
|
|
var callInvoker = SetupClientContext(ctx);
|
|
var client = AccountingServiceClient ?? (callInvoker != null
|
|
? new AccountingService.AccountingServiceClient(callInvoker)
|
|
: new AccountingService.AccountingServiceClient(ClientCtx.Channel));
|
|
|
|
return new AccountingServiceProvider(client, ClientCtx);
|
|
}
|
|
|
|
private ContainerServiceProvider GetContainerService(IContext ctx)
|
|
{
|
|
var callInvoker = SetupClientContext(ctx);
|
|
var client = ContainerServiceClient ?? (callInvoker != null
|
|
? new ContainerService.ContainerServiceClient(callInvoker)
|
|
: new ContainerService.ContainerServiceClient(ClientCtx.Channel));
|
|
|
|
return new ContainerServiceProvider(client, ClientCtx);
|
|
}
|
|
|
|
private ObjectServiceProvider GetObjectService(IContext ctx)
|
|
{
|
|
var callInvoker = SetupClientContext(ctx);
|
|
var client = ObjectServiceClient ?? (callInvoker != null
|
|
? new ObjectService.ObjectServiceClient(callInvoker)
|
|
: new ObjectService.ObjectServiceClient(ClientCtx.Channel));
|
|
|
|
return new ObjectServiceProvider(client, ClientCtx);
|
|
}
|
|
|
|
private AccountingServiceProvider GetAccountService(IContext ctx)
|
|
{
|
|
var callInvoker = SetupClientContext(ctx);
|
|
var client = AccountingServiceClient ?? (callInvoker != null
|
|
? new AccountingService.AccountingServiceClient(callInvoker)
|
|
: new AccountingService.AccountingServiceClient(ClientCtx.Channel));
|
|
|
|
return new AccountingServiceProvider(client, ClientCtx);
|
|
}
|
|
|
|
private static GrpcChannel InitGrpcChannel(string host, GrpcChannelOptions? channelOptions)
|
|
{
|
|
try
|
|
{
|
|
var uri = new Uri(host);
|
|
|
|
if (channelOptions != null)
|
|
return GrpcChannel.ForAddress(uri, channelOptions);
|
|
|
|
return GrpcChannel.ForAddress(uri, new GrpcChannelOptions
|
|
{
|
|
HttpHandler = new HttpClientHandler()
|
|
});
|
|
}
|
|
catch (UriFormatException e)
|
|
{
|
|
throw new ArgumentException($"Host '{host}' has invalid format. Error: {e.Message}");
|
|
}
|
|
}
|
|
|
|
public async Task<string?> Dial(CallContext ctx)
|
|
{
|
|
var prm = new PrmBalance(ctx);
|
|
|
|
var service = GetAccouningService(prm);
|
|
_ = await service.GetBallance(prm).ConfigureAwait(false);
|
|
|
|
return null;
|
|
}
|
|
|
|
public bool RestartIfUnhealthy(CallContext ctx)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
public void Close()
|
|
{
|
|
Dispose();
|
|
}
|
|
}
|