using FrostFS.Container; using FrostFS.Netmap; using FrostFS.Object; using FrostFS.SDK.ClientV2.Interfaces; using FrostFS.SDK.ClientV2.Parameters; 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.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 clientOptions, GrpcChannelOptions? channelOptions = null) { return new Client(clientOptions, channelOptions); } /// /// For test only. Provide custom implementation or mock object to inject required logic instead of internal gRPC client. /// /// Global setting for client /// Setting for gRPC channel /// ContainerService.ContainerServiceClient implementation /// Netmap.NetmapService.NetmapServiceClient implementation /// Session.SessionService.SessionServiceClient implementation /// Object.ObjectService.ObjectServiceClient implementation /// public static IFrostFSClient GetTestInstance( IOptions 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 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 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)); // TODO: define timeout logic CheckFrostFsVersionSupport(new Context { Timeout = TimeSpan.FromSeconds(20)}); } 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 GetContainerAsync(PrmGetContainer args) { var service = GetContainerService(args); return service.GetContainerAsync(args); } public IAsyncEnumerable ListContainersAsync(PrmListContainer? args = null) { args = args ?? new PrmListContainer(); var service = GetContainerService(args); return service.ListContainersAsync(args); } public Task CreateContainerAsync(PrmCreateContainer args) { var service = GetContainerService(args); return service.CreateContainerAsync(args); } public Task DeleteContainerAsync(PrmDeleteContainer args) { var service = GetContainerService(args); return service.DeleteContainerAsync(args); } #endregion #region NetworkImplementation public Task GetNetmapSnapshotAsync(PrmGetNetmapSnapshot? args) { args ??= new PrmGetNetmapSnapshot(); var service = GetNetmapService(args); return service.GetNetmapSnapshotAsync(args); } public Task GetNodeInfoAsync(PrmGetNodeInfo? args) { args ??= new PrmGetNodeInfo(); var service = GetNetmapService(args); return service.GetLocalNodeInfoAsync(args); } public Task GetNetworkSettingsAsync(PrmGetNetworkSettings? args) { args ??= new PrmGetNetworkSettings(); var service = GetNetmapService(args); return service.GetNetworkSettingsAsync(args.Context!); } #endregion #region ObjectImplementation public Task GetObjectHeadAsync(PrmGetObjectHead args) { var service = GetObjectService(args); return service.GetObjectHeadAsync(args); } public Task GetObjectAsync(PrmGetObject args) { var service = GetObjectService(args); return service.GetObjectAsync(args); } public Task PutObjectAsync(PrmPutObject args) { var service = GetObjectService(args); return service.PutObjectAsync(args); } public Task PutSingleObjectAsync(PrmPutSingleObject args) { var service = GetObjectService(args); return service.PutSingleObjectAsync(args); } public Task DeleteObjectAsync(PrmDeleteObject args) { var service = GetObjectService(args); return service.DeleteObjectAsync(args); } public IAsyncEnumerable SearchObjectsAsync(PrmSearchObject args) { var service = GetObjectService(args); return service.SearchObjectsAsync(args); } #endregion #region SessionImplementation public async Task CreateSessionAsync(PrmCreateSession args) { var session = await CreateSessionInternalAsync(args); var token = session.Serialize(); return new ModelsV2.SessionToken(token); } internal Task CreateSessionInternalAsync(PrmCreateSession args) { var service = GetSessionService(args); return service.CreateSessionAsync(args); } #endregion #region ToolsImplementation public ObjectId CalculateObjectId(ObjectHeader header) { return new ObjectTools(ClientCtx).CalculateObjectId(header); } #endregion private async void CheckFrostFsVersionSupport(Context? ctx = default) { var args = new PrmGetNodeInfo(ctx); var service = GetNetmapService(args); var localNodeInfo = await service.GetLocalNodeInfoAsync(args); if (!localNodeInfo.Version.IsSupported(ClientCtx.Version)) { var msg = $"FrostFS {localNodeInfo.Version} is not supported."; Console.WriteLine(msg); throw new ApplicationException(msg); } } private CallInvoker? SetupEnvironment(IContext ctx) { if (isDisposed) throw new Exception("Client is disposed."); ctx.Context ??= new Context(); CallInvoker? callInvoker = null; if (ctx.Context.Interceptors != null && ctx.Context.Interceptors.Count > 0) { foreach (var interceptor in ctx.Context.Interceptors) { callInvoker = AddInvoker(callInvoker, interceptor); } } if (ctx.Context.Callback != null) callInvoker = AddInvoker(callInvoker, new MetricsInterceptor(ctx.Context.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(IContext ctx) { var callInvoker = SetupEnvironment(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 = SetupEnvironment(ctx); var client = SessionServiceClient ?? (callInvoker != null ? new SessionService.SessionServiceClient(callInvoker) : new SessionService.SessionServiceClient(ClientCtx.Channel)); return new SessionServiceProvider(client, ClientCtx); } private ContainerServiceProvider GetContainerService(IContext ctx) { var callInvoker = SetupEnvironment(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 = SetupEnvironment(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) { 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}"); } } }