frostfs-sdk-csharp/src/FrostFS.SDK.ClientV2/Client.cs
Pavel Gross ae67b12313 [#14] Add interceptors
Signed-off-by: Pavel Gross <p.gross@yadro.com>
2024-07-01 15:11:52 +03:00

337 lines
12 KiB
C#

using FrostFS.Container;
using FrostFS.Netmap;
using FrostFS.Object;
using FrostFS.SDK.ClientV2.Interfaces;
using FrostFS.SDK.Cryptography;
using FrostFS.SDK.ModelsV2;
using FrostFS.SDK.ModelsV2.Netmap;
using FrostFS.Session;
using Grpc.Core;
using Grpc.Core.Interceptors;
using Grpc.Net.Client;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Security.Cryptography;
using System.Threading.Tasks;
using Version = FrostFS.SDK.ModelsV2.Version;
namespace FrostFS.SDK.ClientV2;
public class Client : IFrostFSClient
{
private bool isDisposed;
internal ContainerService.ContainerServiceClient? ContainerServiceClient { get; set; }
internal NetmapService.NetmapServiceClient? NetmapServiceClient { get; set; }
internal SessionService.SessionServiceClient? SessionServiceClient { get; set; }
internal ObjectService.ObjectServiceClient? ObjectServiceClient { get; set; }
internal ClientEnvironment ClientCtx { get; set; }
public static IFrostFSClient GetInstance(IOptions<ClientSettings> clientOptions, GrpcChannelOptions? channelOptions = null)
{
return new Client(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<ClientSettings> clientOptions,
GrpcChannelOptions? channelOptions,
NetmapService.NetmapServiceClient netmapService,
SessionService.SessionServiceClient sessionService,
ContainerService.ContainerServiceClient containerService,
ObjectService.ObjectServiceClient objectService)
{
return new Client(clientOptions, channelOptions, containerService, netmapService, sessionService, objectService);
}
private Client(
IOptions<ClientSettings> settings,
GrpcChannelOptions? channelOptions,
ContainerService.ContainerServiceClient containerService,
NetmapService.NetmapServiceClient netmapService,
SessionService.SessionServiceClient sessionService,
ObjectService.ObjectServiceClient objectService)
{
var ecdsaKey = settings.Value.Key.LoadWif();
OwnerId.FromKey(ecdsaKey);
ClientCtx = new ClientEnvironment(
this,
key: ecdsaKey,
owner: OwnerId.FromKey(ecdsaKey),
channel: InitGrpcChannel(settings.Value.Host, channelOptions),
version: new Version(2, 13));
ContainerServiceClient = containerService;
NetmapServiceClient = netmapService;
SessionServiceClient = sessionService;
ObjectServiceClient = objectService;
}
private Client(IOptions<ClientSettings> options, GrpcChannelOptions? channelOptions)
{
var clientSettings = (options?.Value) ?? throw new ArgumentException("Options must be initialized");
clientSettings.Validate();
var ecdsaKey = clientSettings.Key.LoadWif();
var channel = InitGrpcChannel(clientSettings.Host, channelOptions);
ClientCtx = new ClientEnvironment(
this,
key: ecdsaKey,
owner: OwnerId.FromKey(ecdsaKey),
channel: channel,
version: new Version(2, 13));
CheckFrostFsVersionSupport();
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing && !isDisposed)
{
ClientCtx.Dispose();
isDisposed = true;
}
}
#region ContainerImplementation
public Task<ModelsV2.Container> GetContainerAsync(ContainerId containerId, Context? ctx = null)
{
var service = GetContainerService(ref ctx);
return service.GetContainerAsync(containerId, ctx!);
}
public IAsyncEnumerable<ContainerId> ListContainersAsync(Context? ctx = default)
{
var service = GetContainerService(ref ctx);
return service.ListContainersAsync(ctx!);
}
public Task<ContainerId> CreateContainerAsync(ModelsV2.Container container, Context? ctx = null)
{
var service = GetContainerService(ref ctx);
return service.CreateContainerAsync(container, ctx!);
}
public Task DeleteContainerAsync(ContainerId containerId, Context? ctx = default)
{
var service = GetContainerService(ref ctx);
return service.DeleteContainerAsync(containerId, ctx!);
}
#endregion
#region NetworkImplementation
public Task<NetmapSnapshot> GetNetmapSnapshotAsync(Context? ctx = default)
{
var service = GetNetmapService(ref ctx);
return service.GetNetmapSnapshotAsync(ctx!);
}
public Task<ModelsV2.Netmap.NodeInfo> GetNodeInfoAsync(Context? ctx = default)
{
var service = GetNetmapService(ref ctx);
return service.GetLocalNodeInfoAsync(ctx!);
}
public Task<NetworkSettings> GetNetworkSettingsAsync(Context? ctx = default)
{
var service = GetNetmapService(ref ctx);
return service.GetNetworkSettingsAsync(ctx!);
}
#endregion
#region ObjectImplementation
public Task<ObjectHeader> GetObjectHeadAsync(ContainerId containerId, ObjectId objectId, Context? ctx = default)
{
var service = GetObjectService(ref ctx);
return service.GetObjectHeadAsync(containerId, objectId, ctx!);
}
public Task<ModelsV2.Object> GetObjectAsync(ContainerId containerId, ObjectId objectId, Context? ctx = default)
{
var service = GetObjectService(ref ctx);
return service.GetObjectAsync(containerId, objectId, ctx!);
}
public Task<ObjectId> PutObjectAsync(PutObjectParameters putObjectParameters, Context? ctx = default)
{
var service = GetObjectService(ref ctx);
return service.PutObjectAsync(putObjectParameters, ctx!);
}
public Task<ObjectId> PutSingleObjectAsync(ModelsV2.Object obj, Context? ctx = default)
{
var service = GetObjectService(ref ctx);
return service.PutSingleObjectAsync(obj, ctx!);
}
public Task DeleteObjectAsync(ContainerId containerId, ObjectId objectId, Context? ctx = default)
{
var service = GetObjectService(ref ctx);
return service.DeleteObjectAsync(containerId, objectId, ctx!);
}
public IAsyncEnumerable<ObjectId> SearchObjectsAsync(
ContainerId containerId,
IEnumerable<ObjectFilter> filters,
Context? ctx = default)
{
var service = GetObjectService(ref ctx);
return service.SearchObjectsAsync(containerId, filters, ctx!);
}
#endregion
#region SessionImplementation
public async Task<ModelsV2.SessionToken> CreateSessionAsync(ulong expiration, Context? ctx = null)
{
var session = await CreateSessionInternalAsync(expiration, ctx);
var token = session.Serialize();
return new ModelsV2.SessionToken([], token);
}
public Task<Session.SessionToken> CreateSessionInternalAsync(ulong expiration, Context? ctx = null)
{
var service = GetSessionService(ref ctx);
return service.CreateSessionAsync(expiration, ctx!);
}
#endregion
#region ToolsImplementation
public ObjectId CalculateObjectId(ObjectHeader header)
{
return new ObjectTools(ClientCtx).CalculateObjectId(header);
}
#endregion
private async void CheckFrostFsVersionSupport(Context? ctx = default)
{
var service = GetNetmapService(ref ctx);
var localNodeInfo = await service.GetLocalNodeInfoAsync(ctx!);
if (!localNodeInfo.Version.IsSupported(ClientCtx.Version))
{
var msg = $"FrostFS {localNodeInfo.Version} is not supported.";
Console.WriteLine(msg);
throw new ApplicationException(msg);
}
}
private CallInvoker? SetupEnvironment(ref Context? ctx)
{
if (isDisposed)
throw new Exception("Client is disposed.");
ctx ??= new Context();
CallInvoker? callInvoker = null;
if (ctx.Interceptors != null && ctx.Interceptors.Count > 0)
{
foreach (var interceptor in ctx.Interceptors)
{
callInvoker = AddInvoker(callInvoker, interceptor);
}
}
if (ctx.Callback != null)
callInvoker = AddInvoker(callInvoker, new MetricsInterceptor(ctx.Callback));
return callInvoker;
CallInvoker AddInvoker(CallInvoker? callInvoker, Interceptor interceptor)
{
if (callInvoker == null)
callInvoker = ClientCtx.Channel.Intercept(interceptor);
else
callInvoker.Intercept(interceptor);
return callInvoker;
}
}
private NetmapServiceProvider GetNetmapService(ref Context? ctx)
{
var callInvoker = SetupEnvironment(ref ctx);
var client = NetmapServiceClient ?? (callInvoker != null
? new NetmapService.NetmapServiceClient(callInvoker)
: new NetmapService.NetmapServiceClient(ClientCtx.Channel));
return new NetmapServiceProvider(client, ClientCtx);
}
private SessionServiceProvider GetSessionService(ref Context? ctx)
{
var callInvoker = SetupEnvironment(ref ctx);
var client = SessionServiceClient ?? (callInvoker != null
? new SessionService.SessionServiceClient(callInvoker)
: new SessionService.SessionServiceClient(ClientCtx.Channel));
return new SessionServiceProvider(client, ClientCtx);
}
private ContainerServiceProvider GetContainerService(ref Context? ctx)
{
var callInvoker = SetupEnvironment(ref ctx);
var client = ContainerServiceClient ?? (callInvoker != null
? new ContainerService.ContainerServiceClient(callInvoker)
: new ContainerService.ContainerServiceClient(ClientCtx.Channel));
return new ContainerServiceProvider(client, ClientCtx);
}
private ObjectServiceProvider GetObjectService(ref Context? ctx)
{
var callInvoker = SetupEnvironment(ref ctx);
var client = ObjectServiceClient ?? (callInvoker != null
? new ObjectService.ObjectServiceClient(callInvoker)
: new ObjectService.ObjectServiceClient(ClientCtx.Channel));
return new ObjectServiceProvider(client, ClientCtx);
}
private static GrpcChannel InitGrpcChannel(string host, GrpcChannelOptions? channelOptions)
{
Uri uri;
try
{
uri = new Uri(host);
}
catch (UriFormatException e)
{
var msg = $"Host '{host}' has invalid format. Error: {e.Message}";
throw new ArgumentException(msg);
}
if (channelOptions != null)
{
return GrpcChannel.ForAddress(uri, channelOptions);
}
return GrpcChannel.ForAddress(uri, new GrpcChannelOptions
{
HttpHandler = new HttpClientHandler()
});
}
}