Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use DI container #217

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion dotnet-etcd/Util.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

namespace dotnet_etcd
{
public partial class EtcdClient
internal partial class EtcdClient
{
private const string rangeEndString = "\x00";
/// <summary>
Expand Down
2 changes: 1 addition & 1 deletion dotnet-etcd/authClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

namespace dotnet_etcd
{
public partial class EtcdClient
internal partial class EtcdClient
{
/// <summary>
/// Authenticate processes an authenticate request.
Expand Down
2 changes: 1 addition & 1 deletion dotnet-etcd/clusterClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

namespace dotnet_etcd
{
public partial class EtcdClient
internal partial class EtcdClient
{
/// <summary>
/// MemberAdd adds a member into the cluster
Expand Down
7 changes: 4 additions & 3 deletions dotnet-etcd/dotnet-etcd.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,16 @@ Advanced uses take advantage of the consistency guarantees to implement database
<ItemGroup>
<None Remove="etcd-logo-rectangle.png" />
<None Remove="grpc\proto\google\api\annotations.proto" />
<None Include="..\README.md" Pack ="True" PackagePath="\" />
<None Include="etcd-logo-rectangle.png" Pack ="True" PackagePath="\" />
<None Include="..\README.md" Pack="True" PackagePath="\" />
<None Include="etcd-logo-rectangle.png" Pack="True" PackagePath="\" />
</ItemGroup>


<ItemGroup>
<PackageReference Include="Google.Api.CommonProtos" Version="2.13.0" />
<PackageReference Include="Google.Api.CommonProtos" Version="2.13.0" />
<PackageReference Include="Google.Protobuf" Version="3.25.1" />
<PackageReference Include="Grpc.Net.Client" Version="2.60.0" />
<PackageReference Include="Grpc.Net.ClientFactory" Version="2.60.0" />
<PackageReference Include="Grpc.Tools" Version="2.60.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
Expand Down
177 changes: 17 additions & 160 deletions dotnet-etcd/etcdClient.cs
Original file line number Diff line number Diff line change
@@ -1,184 +1,41 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net.Http;


using dotnet_etcd.interfaces;
using dotnet_etcd.multiplexer;

using Etcdserverpb;

using Grpc.Core;
using Grpc.Core.Interceptors;
using Grpc.Net.Client;
using Grpc.Net.Client.Balancer;
using Grpc.Net.Client.Configuration;

using Microsoft.Extensions.DependencyInjection;
using V3Lockpb;

namespace dotnet_etcd
{
/// <summary>
/// Etcd client is the entrypoint for this library.
/// It contains all the functions required to perform operations on etcd.
/// </summary>
public partial class EtcdClient : IDisposable, IEtcdClient
internal partial class EtcdClient : IEtcdClient
{
#region Variables

private const string InsecurePrefix = "http://";
private const string SecurePrefix = "https://";

private const string StaticHostsPrefix = "static://";
private const string DnsPrefix = "dns://";
private const string AlternateDnsPrefix = "discovery-srv://";

private const string DefaultServerName = "my-etcd-server";

private readonly Connection _connection;

// https://learn.microsoft.com/en-us/aspnet/core/grpc/retries?view=aspnetcore-6.0#configure-a-grpc-retry-policy
private static readonly MethodConfig _defaultGrpcMethodConfig = new()
{
Names = { MethodName.Default },
RetryPolicy = new RetryPolicy
{
MaxAttempts = 5,
InitialBackoff = TimeSpan.FromSeconds(1),
MaxBackoff = TimeSpan.FromSeconds(5),
BackoffMultiplier = 1.5,
RetryableStatusCodes = { StatusCode.Unavailable }
}
};

// https://github.com/grpc/proposal/blob/master/A6-client-retries.md#throttling-retry-attempts-and-hedged-rpcs
private static readonly RetryThrottlingPolicy _defaultRetryThrottlingPolicy = new()
public EtcdClient(
KV.KVClient kvClient,
Watch.WatchClient watchClient,
Lease.LeaseClient leaseClient,
Lock.LockClient lockClient,
Cluster.ClusterClient clusterClient,
Maintenance.MaintenanceClient maintenanceClient,
Auth.AuthClient authClient)
{
MaxTokens = 10,
TokenRatio = 0.1
};
#endregion

#region Initializers

public EtcdClient(string connectionString, int port = 2379, string serverName = DefaultServerName, Action<GrpcChannelOptions> configureChannelOptions = null, Interceptor[] interceptors = null)
{

// Param check
if (string.IsNullOrWhiteSpace(connectionString))
{
throw new ArgumentNullException(nameof(connectionString));
}

// Param sanitization

interceptors ??= Array.Empty<Interceptor>();

if (connectionString.StartsWith(AlternateDnsPrefix, StringComparison.InvariantCultureIgnoreCase))
{
connectionString = connectionString.Substring(AlternateDnsPrefix.Length);
connectionString = DnsPrefix + connectionString;
}

// Connection Configuration
var options = new GrpcChannelOptions
{
ServiceConfig = new ServiceConfig
{
MethodConfigs = { _defaultGrpcMethodConfig },
RetryThrottling = _defaultRetryThrottlingPolicy,
LoadBalancingConfigs = { new RoundRobinConfig() },
}
};

configureChannelOptions?.Invoke(options);

// Channel Configuration
GrpcChannel channel = null;
if (connectionString.StartsWith(DnsPrefix, StringComparison.InvariantCultureIgnoreCase))
{
channel = GrpcChannel.ForAddress(connectionString, options);
}
else
{
string[] hosts = Array.Empty<string>();
hosts = connectionString.Split(',');
List<Uri> nodes = new();
for (int i = 0; i < hosts.Length; i++)
{
string host = hosts[i];
if (host.Split(':').Length < 3)
{
host += $":{Convert.ToString(port, CultureInfo.InvariantCulture)}";
}

if (!(host.StartsWith(InsecurePrefix, StringComparison.InvariantCultureIgnoreCase) || host.StartsWith(SecurePrefix, StringComparison.InvariantCultureIgnoreCase)))
{
host = options.Credentials == ChannelCredentials.Insecure ? $"{InsecurePrefix}{host}" : $"{SecurePrefix}{host}";
}

nodes.Add(new Uri(host));
}

var factory = new StaticResolverFactory(addr => nodes.Select(i => new BalancerAddress(i.Host, i.Port)).ToArray());
var services = new ServiceCollection();
services.AddSingleton<ResolverFactory>(factory);
options.ServiceProvider = services.BuildServiceProvider();

channel = GrpcChannel.ForAddress($"{StaticHostsPrefix}{serverName}", options);
}

CallInvoker callInvoker = interceptors != null && interceptors.Length > 0 ? channel.Intercept(interceptors) : channel.CreateCallInvoker();


// Setup Connection
_connection = new Connection
{
_kvClient = new KV.KVClient(callInvoker),
_watchClient = new Watch.WatchClient(callInvoker),
_leaseClient = new Lease.LeaseClient(callInvoker),
_lockClient = new V3Lockpb.Lock.LockClient(callInvoker),
_clusterClient = new Cluster.ClusterClient(callInvoker),
_maintenanceClient = new Maintenance.MaintenanceClient(callInvoker),
_authClient = new Auth.AuthClient(callInvoker)
_kvClient = kvClient,
_watchClient = watchClient,
_leaseClient = leaseClient,
_lockClient = lockClient,
_clusterClient = clusterClient,
_maintenanceClient = maintenanceClient,
_authClient = authClient
};
}

#endregion

#region IDisposable Support

private bool _disposed; // To detect redundant calls

protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
// TODO: dispose managed state (managed objects).
}

// TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
// TODO: set large fields to null.
_disposed = true;
}
}

// This code added to correctly implement the disposable pattern.
public void Dispose()
{
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
Dispose(true);
// TODO: uncomment the following line if the finalizer is overridden above.
GC.SuppressFinalize(this);
}

#endregion
}
}
Loading