diff --git a/README.md b/README.md
index 1f08f0b..5473c34 100644
--- a/README.md
+++ b/README.md
@@ -595,7 +595,8 @@ In case you need consistent cache across clusters or data centers you can use ca
"Interval": "00:01:00",
"MaxErrors": 50,
"SwitchOffTime": "00:02:00"
- }
+ },
+ "SyncEndpointsAuthAllowAnonymous": false
}
}
}
@@ -626,6 +627,8 @@ app.UseEndpoints(endpoints =>
`Configuration` argument here is a property on a `Startup` instance
+If you have implicit authorization configured for your service you can allow anonymous access to sync endpoints by setting `MemcachedConfiguration.SyncSettings.SyncEndpointsAuthAllowAnonymous` to `true`.
+
When using cache synchronization feature, the `MemcachedClientResult.SyncSuccess` property can be inspected to determine whether the sync operation succeeded. When cache synchronization is not used this property is set to `false`.
To check whether the cache synchronization is configured and enabled call the `IMemcachedClient.IsCacheSyncEnabled` method.
diff --git a/src/Aer.Memcached.Client/Config/MemcachedConfiguration.cs b/src/Aer.Memcached.Client/Config/MemcachedConfiguration.cs
index 5c58fc7..57026ce 100644
--- a/src/Aer.Memcached.Client/Config/MemcachedConfiguration.cs
+++ b/src/Aer.Memcached.Client/Config/MemcachedConfiguration.cs
@@ -341,6 +341,11 @@ public class SynchronizationSettings
/// Settings of circuit breaker.
///
public CacheSyncCircuitBreakerSettings CacheSyncCircuitBreaker { get; set; }
+
+ ///
+ /// If set to true allows anonymous access to cache sync endpoints.
+ ///
+ public bool SyncEndpointsAuthAllowAnonymous { get; set; }
}
public class SyncServer
diff --git a/src/Aer.Memcached.Client/ConnectionPool/PooledSocket.cs b/src/Aer.Memcached.Client/ConnectionPool/PooledSocket.cs
index 25e44b5..131076a 100644
--- a/src/Aer.Memcached.Client/ConnectionPool/PooledSocket.cs
+++ b/src/Aer.Memcached.Client/ConnectionPool/PooledSocket.cs
@@ -202,7 +202,7 @@ public async Task WriteAsync(IList> buffers)
///
/// Releases all resources used by this instance and shuts down the inner . This instance will not be usable anymore.
///
- /// Use the method if you want to release this instance back into the pool.
+ /// Use the method if you want to release this instance back into the pool.
public void Destroy()
{
Dispose(true);
diff --git a/src/Aer.Memcached.Client/Models/MemcachedClientValueResult.cs b/src/Aer.Memcached.Client/Models/MemcachedClientValueResult.cs
index bcf0dca..a2c3bd2 100644
--- a/src/Aer.Memcached.Client/Models/MemcachedClientValueResult.cs
+++ b/src/Aer.Memcached.Client/Models/MemcachedClientValueResult.cs
@@ -83,6 +83,7 @@ internal static MemcachedClientValueResult Cancelled(string operationName)
///
/// Creates an instance of that indicates request cancellation.
///
+ /// The memcached operation that was cancelled.
/// The default value for property.
internal static MemcachedClientValueResult Cancelled(string operationName, T defaultResultValue)
=> new(
diff --git a/src/Aer.Memcached/Abstractions/INodeHealthChecker.cs b/src/Aer.Memcached/Abstractions/INodeHealthChecker.cs
index 6efad56..006c078 100644
--- a/src/Aer.Memcached/Abstractions/INodeHealthChecker.cs
+++ b/src/Aer.Memcached/Abstractions/INodeHealthChecker.cs
@@ -2,7 +2,11 @@
namespace Aer.Memcached.Abstractions;
-public interface INodeHealthChecker where TNode: class, INode
+internal interface INodeHealthChecker where TNode: class, INode
{
+ ///
+ /// Checks if the given node is dead.
+ ///
+ /// The node to check.
Task CheckNodeIsDeadAsync(TNode node);
}
\ No newline at end of file
diff --git a/src/Aer.Memcached/Abstractions/INodeProvider.cs b/src/Aer.Memcached/Abstractions/INodeProvider.cs
index e3e3768..5caab18 100644
--- a/src/Aer.Memcached/Abstractions/INodeProvider.cs
+++ b/src/Aer.Memcached/Abstractions/INodeProvider.cs
@@ -2,9 +2,19 @@
namespace Aer.Memcached.Abstractions;
-public interface INodeProvider where TNode : class, INode
+///
+/// Provides a collection of nodes for the consistent hashing algorithm.
+///
+/// The type of the node.
+internal interface INodeProvider where TNode : class, INode
{
+ ///
+ /// Indicates whether the node provider is properly configured and ready to provide nodes.
+ ///
bool IsConfigured();
+ ///
+ /// Gets the collection of nodes.
+ ///
ICollection GetNodes();
}
\ No newline at end of file
diff --git a/src/Aer.Memcached/Aer.Memcached.csproj b/src/Aer.Memcached/Aer.Memcached.csproj
index abdf97c..30d9e30 100644
--- a/src/Aer.Memcached/Aer.Memcached.csproj
+++ b/src/Aer.Memcached/Aer.Memcached.csproj
@@ -41,6 +41,9 @@
<_Parameter1>$(MSBuildProjectName).Tests
+
+ <_Parameter1>DynamicProxyGenAssembly2
+
diff --git a/src/Aer.Memcached/Diagnostics/Configuration/MetricsProviderType.cs b/src/Aer.Memcached/Diagnostics/Configuration/MetricsProviderType.cs
index 92b50f9..ac5e306 100644
--- a/src/Aer.Memcached/Diagnostics/Configuration/MetricsProviderType.cs
+++ b/src/Aer.Memcached/Diagnostics/Configuration/MetricsProviderType.cs
@@ -2,6 +2,6 @@
internal enum MetricsProviderType
{
- Prometheus,
- OpenTelemetry
+ Prometheus,
+ OpenTelemetry
}
diff --git a/src/Aer.Memcached/Diagnostics/Listeners/LoggingMemcachedDiagnosticListener.cs b/src/Aer.Memcached/Diagnostics/Listeners/LoggingMemcachedDiagnosticListener.cs
index 3acaab3..e58e133 100644
--- a/src/Aer.Memcached/Diagnostics/Listeners/LoggingMemcachedDiagnosticListener.cs
+++ b/src/Aer.Memcached/Diagnostics/Listeners/LoggingMemcachedDiagnosticListener.cs
@@ -6,7 +6,7 @@
namespace Aer.Memcached.Diagnostics.Listeners;
-public class LoggingMemcachedDiagnosticListener
+internal class LoggingMemcachedDiagnosticListener
{
private readonly ILogger _logger;
private readonly MemcachedConfiguration _config;
diff --git a/src/Aer.Memcached/Diagnostics/Listeners/MetricsMemcachedDiagnosticListener.cs b/src/Aer.Memcached/Diagnostics/Listeners/MetricsMemcachedDiagnosticListener.cs
index dff6cbd..6c95783 100644
--- a/src/Aer.Memcached/Diagnostics/Listeners/MetricsMemcachedDiagnosticListener.cs
+++ b/src/Aer.Memcached/Diagnostics/Listeners/MetricsMemcachedDiagnosticListener.cs
@@ -10,7 +10,9 @@ internal class MetricsMemcachedDiagnosticListener
private readonly MemcachedMetricsProvider _metricsProvider;
private readonly MemcachedConfiguration _config;
- public MetricsMemcachedDiagnosticListener(MemcachedMetricsProvider metricsProvider, IOptions config)
+ public MetricsMemcachedDiagnosticListener(
+ MemcachedMetricsProvider metricsProvider,
+ IOptions config)
{
_metricsProvider = metricsProvider;
_config = config.Value;
@@ -23,10 +25,10 @@ public void ObserveCommandDuration(string commandName, double duration)
{
return;
}
-
+
_metricsProvider.ObserveCommandDurationSeconds(commandName, duration);
}
-
+
[DiagnosticName(MemcachedDiagnosticSource.CommandsTotalDiagnosticName)]
public void ObserveCommandsTotal(string commandName, string isSuccessful)
{
@@ -34,7 +36,7 @@ public void ObserveCommandsTotal(string commandName, string isSuccessful)
{
return;
}
-
+
_metricsProvider.ObserveExecutedCommand(commandName, isSuccessful);
}
@@ -45,7 +47,7 @@ public void ObserveSocketPoolUsedSocketsCount(string enpointAddress, int usedSoc
{
return;
}
-
+
_metricsProvider.ObserveSocketPoolUsedSocketsCount(enpointAddress, usedSocketCount);
}
}
\ No newline at end of file
diff --git a/src/Aer.Memcached/Diagnostics/MemcachedMetricsProvider.cs b/src/Aer.Memcached/Diagnostics/MemcachedMetricsProvider.cs
index 7d5321e..df9a708 100644
--- a/src/Aer.Memcached/Diagnostics/MemcachedMetricsProvider.cs
+++ b/src/Aer.Memcached/Diagnostics/MemcachedMetricsProvider.cs
@@ -26,8 +26,8 @@ internal class MemcachedMetricsProvider
public static readonly Dictionary MetricsBuckets = new()
{
- [CommandDurationSecondsMetricName] = new[] {0.0005, 0.001, 0.005, 0.007, 0.015, 0.05, 0.2, 0.5, 1},
- [SocketPoolUsedSocketsCountsMetricName] = new[] {0, 10.0, 20.0, 50.0, 100.0, 200.0, 500.0}
+ [CommandDurationSecondsMetricName] = [0.0005, 0.001, 0.005, 0.007, 0.015, 0.05, 0.2, 0.5, 1],
+ [SocketPoolUsedSocketsCountsMetricName] = [0, 10.0, 20.0, 50.0, 100.0, 200.0, 500.0]
};
private const string CommandNameLabel = "command_name";
@@ -79,18 +79,18 @@ public MemcachedMetricsProvider(
CommandDurationSecondsMetricName,
"",
MetricsBuckets[CommandDurationSecondsMetricName],
- labelNames: new[] {CommandNameLabel});
+ labelNames: [CommandNameLabel]);
_socketPoolUsedSocketsCounts = metricFactory.CreateHistogram(
SocketPoolUsedSocketsCountsMetricName,
"",
MetricsBuckets[SocketPoolUsedSocketsCountsMetricName],
- labelNames: new[] {SocketPoolEndpointAddressLabel});
+ labelNames: [SocketPoolEndpointAddressLabel]);
_commandsTotal = metricFactory.CreateCounter(
CommandsTotalOtelMetricName,
"",
- labelNames: new[] {CommandNameLabel, IsSuccessfulLabel});
+ labelNames: [CommandNameLabel, IsSuccessfulLabel]);
}
///
@@ -129,7 +129,7 @@ public void ObserveExecutedCommand(string commandName, string isSuccessful)
///
/// Observes specified endpoint socket pool used sockets count.
///
- /// The address of an endpoint to obeserve socket pool state for.
+ /// The address of an endpoint to observe socket pool state for.
/// The number of currently used sockets for the specified pool.
public void ObserveSocketPoolUsedSocketsCount(string endpointAddress, int usedSocketCount)
{
diff --git a/src/Aer.Memcached/Helpers/EndpointBuilderExtensions.cs b/src/Aer.Memcached/Helpers/EndpointBuilderExtensions.cs
new file mode 100644
index 0000000..f4e52d9
--- /dev/null
+++ b/src/Aer.Memcached/Helpers/EndpointBuilderExtensions.cs
@@ -0,0 +1,24 @@
+using Aer.Memcached.Client.Config;
+using Microsoft.AspNetCore.Builder;
+
+namespace Aer.Memcached.Helpers;
+
+internal static class EndpointBuilderExtensions
+{
+ public static RouteHandlerBuilder AllowAnonymousIfConfigured(
+ this RouteHandlerBuilder endpointBuilder,
+ MemcachedConfiguration configuration)
+ {
+ if (configuration.SyncSettings == null)
+ {
+ return endpointBuilder;
+ }
+
+ if (configuration.SyncSettings.SyncEndpointsAuthAllowAnonymous)
+ {
+ endpointBuilder.AllowAnonymous();
+ }
+
+ return endpointBuilder;
+ }
+}
diff --git a/src/Aer.Memcached/Pod.cs b/src/Aer.Memcached/Pod.cs
index 9d74ad1..98162d9 100644
--- a/src/Aer.Memcached/Pod.cs
+++ b/src/Aer.Memcached/Pod.cs
@@ -4,14 +4,29 @@
namespace Aer.Memcached;
-public class Pod: INode
+///
+/// Represents a Memcached pod (node) in the cluster.
+///
+public class Pod : INode
{
private readonly string _nodeKey;
+ ///
+ /// The IP address of the Memcached pod.
+ ///
public string IpAddress { get; }
-
+
+ ///
+ /// The port on which the Memcached pod is listening.
+ ///
public int MemcachedPort { get; }
+ ///
+ /// Initializes a new instance of the class with the specified IP address and port.
+ ///
+ /// The IP address of the Memcached pod.
+ /// The port on which the Memcached pod is listening.
+ /// Occurs when either or is not specified or empty.
public Pod(string ipAddress, int port = MemcachedConfiguration.DefaultMemcachedPort)
{
if (ipAddress is null or {Length: 0})
@@ -26,30 +41,38 @@ public Pod(string ipAddress, int port = MemcachedConfiguration.DefaultMemcachedP
IpAddress = ipAddress;
MemcachedPort = port;
-
- _nodeKey = $"{IpAddress}:{MemcachedPort}";
+
+ _nodeKey = $"{IpAddress}:{MemcachedPort}";
}
+ ///
public string GetKey()
{
return _nodeKey;
}
+ ///
public EndPoint GetEndpoint()
{
return new DnsEndPoint(IpAddress, MemcachedPort);
}
+ ///
+ /// Determines whether the specified Pod is equal to the current Pod.
+ ///
+ /// The pod to compare this pod to.
protected bool Equals(Pod other)
{
return IpAddress == other.IpAddress && MemcachedPort == other.MemcachedPort;
}
+ ///
public bool Equals(INode other)
{
return GetKey() == other?.GetKey();
}
+ ///
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
@@ -67,15 +90,16 @@ public override bool Equals(object obj)
return false;
}
- return Equals((Pod)obj);
+ return Equals((Pod) obj);
}
+ ///
public override int GetHashCode()
{
var ipAddressHash = IpAddress.GetHashCode();
var portHash = MemcachedPort.GetHashCode();
-
+
return HashCode.Combine(ipAddressHash, portHash);
}
}
\ No newline at end of file
diff --git a/src/Aer.Memcached/ServiceCollectionExtensions.cs b/src/Aer.Memcached/ServiceCollectionExtensions.cs
index 48dd140..a914fdc 100644
--- a/src/Aer.Memcached/ServiceCollectionExtensions.cs
+++ b/src/Aer.Memcached/ServiceCollectionExtensions.cs
@@ -14,6 +14,7 @@
using Aer.Memcached.Client.Serializers;
using Aer.Memcached.Diagnostics;
using Aer.Memcached.Diagnostics.Listeners;
+using Aer.Memcached.Helpers;
using Aer.Memcached.Infrastructure;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
@@ -28,8 +29,16 @@
namespace Aer.Memcached;
+///
+/// The extension methods for setting up Memcached services in an .
+///
public static class ServiceCollectionExtensions
{
+ ///
+ /// Adds Memcached services to the with settings from app configuration.
+ ///
+ /// The service collection.
+ /// The application configuration.
public static IServiceCollection AddMemcached(
this IServiceCollection services,
IConfiguration configuration)
@@ -108,6 +117,11 @@ private static void AddOpenTelemetryMetrics(this IServiceCollection services, st
});
}
+ ///
+ /// Enables Memcached diagnostics listeners for metrics and logging.
+ ///
+ /// The application builder instance.
+ /// The application configuration.
public static IApplicationBuilder EnableMemcachedDiagnostics(
this IApplicationBuilder applicationBuilder,
IConfiguration configuration)
@@ -132,15 +146,27 @@ public static IApplicationBuilder EnableMemcachedDiagnostics(
return applicationBuilder;
}
- public static void AddMemcachedEndpoints(this IEndpointRouteBuilder endpoints, IConfiguration configuration)
+ ///
+ /// Adds endpoints for internode synchronization to the with settings from app configuration.
+ ///
+ /// The service endpoints route builder instance.
+ /// The application configuration.
+ public static void AddMemcachedEndpoints(
+ this IEndpointRouteBuilder endpoints,
+ IConfiguration configuration)
{
- var config = configuration.GetSection(nameof(MemcachedConfiguration)).Get();
+ var config = configuration
+ .GetSection(nameof(MemcachedConfiguration))
+ .Get();
+
var deleteEndpoint = config.SyncSettings == null
? MemcachedConfiguration.DefaultDeleteEndpoint
: config.SyncSettings.DeleteEndpoint;
+
var flushEndpoint = config.SyncSettings == null
? MemcachedConfiguration.DefaultFlushEndpoint
: config.SyncSettings.FlushEndpoint;
+
var getEndpoint = config.SyncSettings == null
? MemcachedConfiguration.DefaultGetEndpoint
: config.SyncSettings.GetEndpoint;
@@ -159,15 +185,17 @@ await memcachedClient.MultiDeleteAsync(
{
IsManualSyncOn = false
});
- });
+ }
+ ).AllowAnonymousIfConfigured(config);
endpoints.MapPost(
flushEndpoint,
async (IMemcachedClient memcachedClient, CancellationToken token) =>
{
await memcachedClient.FlushAsync(token);
- });
-
+ }
+ ).AllowAnonymousIfConfigured(config);
+
if (config.SyncSettings != null)
{
endpoints.MapPost(
@@ -183,12 +211,13 @@ await memcachedClient.MultiStoreSynchronizeDataAsync(
model.ExpirationTime,
token,
model.ExpirationMap);
- });
+ }
+ ).AllowAnonymousIfConfigured(config);
}
-
+
endpoints.MapPost(
getEndpoint,
- (MultiGetTypedRequest request, IMemcachedClient memcachedClient, CancellationToken token) =>
+ (MultiGetTypedRequest request, IMemcachedClient memcachedClient, CancellationToken token) =>
{
try
{
@@ -198,30 +227,34 @@ await memcachedClient.MultiStoreSynchronizeDataAsync(
return Results.Ok(
$"Type is not found. Try {typeof(string).FullName} or {typeof(object).FullName}");
}
-
+
var method = typeof(MemcachedClient).GetMethod(nameof(MemcachedClient.MultiGetAsync));
if (method == null)
{
return Results.Ok($"Method for the type {resolvedType} is not found");
}
-
+
var genericMethod = method.MakeGenericMethod(resolvedType);
-
- var task = genericMethod.Invoke(memcachedClient, parameters: [request.Keys, token, null, (uint)0]) as Task;
+
+ var task =
+ genericMethod.Invoke(
+ memcachedClient,
+ parameters: [request.Keys, token, null, (uint) 0]) as Task;
if (task == null)
{
- return Results.Ok($"Method for the type {resolvedType} is not found");
+ return Results.Ok($"Method for the type {resolvedType} is not found");
}
-
+
var result = task.GetType().GetProperty("Result")?.GetValue(task);
-
+
return Results.Ok(Newtonsoft.Json.JsonConvert.SerializeObject(result));
}
catch (Exception e)
{
return Results.BadRequest(e);
}
- });
+ }
+ ).AllowAnonymousIfConfigured(config);
}
private class MultiGetTypedRequest
diff --git a/tests/Aer.Memcached.Tests/Base/MemcachedClientTestsBase.cs b/tests/Aer.Memcached.Tests/Base/MemcachedClientTestsBase.cs
index b29237f..d08c50b 100644
--- a/tests/Aer.Memcached.Tests/Base/MemcachedClientTestsBase.cs
+++ b/tests/Aer.Memcached.Tests/Base/MemcachedClientTestsBase.cs
@@ -24,7 +24,7 @@ public abstract class MemcachedClientTestsBase
protected readonly Fixture Fixture;
- protected readonly ObjectBinarySerializerType BinarySerizerType;
+ protected readonly ObjectBinarySerializerType BinarySerializerType;
protected readonly ServiceProvider ServiceProvider;
@@ -33,7 +33,7 @@ protected MemcachedClientTestsBase(
ObjectBinarySerializerType binarySerializerType = ObjectBinarySerializerType.Bson,
bool isAllowLongKeys = false)
{
- BinarySerizerType = binarySerializerType;
+ BinarySerializerType = binarySerializerType;
var hashCalculator = new HashCalculator();
@@ -70,7 +70,10 @@ protected MemcachedClientTestsBase(
SocketPoolDiagnosticsLoggingEventLevel = LogLevel.Information
},
BinarySerializerType = binarySerializerType,
- IsAllowLongKeys = isAllowLongKeys
+ IsAllowLongKeys = isAllowLongKeys,
+ SyncSettings = new (){
+ SyncEndpointsAuthAllowAnonymous = false
+ }
};
var authProvider = new DefaultAuthenticationProvider(
diff --git a/tests/Aer.Memcached.Tests/TestClasses/MemcachedClientMethodsTestsBase.cs b/tests/Aer.Memcached.Tests/TestClasses/MemcachedClientMethodsTestsBase.cs
index 2cce562..d588ae2 100644
--- a/tests/Aer.Memcached.Tests/TestClasses/MemcachedClientMethodsTestsBase.cs
+++ b/tests/Aer.Memcached.Tests/TestClasses/MemcachedClientMethodsTestsBase.cs
@@ -51,7 +51,7 @@ public async Task StoreAndGet_CheckAllTypes()
await StoreAndGet_CheckType>();
await StoreAndGet_CheckType();
- if (BinarySerizerType != ObjectBinarySerializerType.Bson)
+ if (BinarySerializerType != ObjectBinarySerializerType.Bson)
{
// BSON serializer can't serialize dictionaries with non-primitive objects as keys
await StoreAndGet_CheckType>();
@@ -82,7 +82,7 @@ public async Task MultiStoreAndGet_CheckAllTypes(bool withReplicas)
await MultiStoreAndGet_CheckType>(withReplicas);
await MultiStoreAndGet_CheckType(withReplicas);
- if (BinarySerizerType != ObjectBinarySerializerType.Bson)
+ if (BinarySerializerType != ObjectBinarySerializerType.Bson)
{
// BSON serializer can't serialize dictionaries with non-primitive objects as keys
await StoreAndGet_CheckType>();
@@ -121,7 +121,7 @@ public async Task Get_CheckAllTypes_DefaultValue()
await Get_CheckType>();
await Get_CheckType();
- if (BinarySerizerType != ObjectBinarySerializerType.Bson)
+ if (BinarySerializerType != ObjectBinarySerializerType.Bson)
{
// BSON serializer can't serialize dictionaries with non-primitive objects as keys
await StoreAndGet_CheckType>();
@@ -151,7 +151,7 @@ public async Task MultiGet_CheckAllTypes_EmptyDictionary(bool withReplicas)
await MultiGet_CheckType(withReplicas);
await MultiGet_CheckType>(withReplicas);
- if (BinarySerizerType != ObjectBinarySerializerType.Bson)
+ if (BinarySerializerType != ObjectBinarySerializerType.Bson)
{
// BSON serializer can't serialize dictionaries with non-primitive objects as keys
await StoreAndGet_CheckType>();
@@ -715,7 +715,7 @@ public async Task StoreAndGet_RecursiveModel()
[TestMethod]
public async Task StoreAndGet_ObjectWithNested_IgnoreReferenceLoopHandling()
{
- if (BinarySerizerType != ObjectBinarySerializerType.Bson)
+ if (BinarySerializerType != ObjectBinarySerializerType.Bson)
{
// only BSON serializer can ignore reference loops
return;