Skip to content

Commit

Permalink
Supporting CLIENT identification via SETINFO (#180)
Browse files Browse the repository at this point in the history
* deprecated redisGraph

* supporting client SetInfo command

* add tests

* check somthing

* check2

* Async

* try ConnectionMultiplexer.Connect("localhost");

* new format

* fix core tests

* try change to IDatabase instead of IDatabaseAsync

* add bool _setinfo to Auxiliary

* comment CoreTests

* fix Aux

* change test

* fix tests

* not using pipeline

* move _setInfo = false;

* delete unused key

* if redis version less than 7.1.242 dont sent SETINFO

* add sleep

* no sleep

* try setinfo to true in each test

* reset info defaults in each test

* skip cluster + send commands in pipeline

* delete comments

* add null test

* compare and of strings in null test

* fixes
  • Loading branch information
shacharPash committed Oct 19, 2023
1 parent da200b6 commit aa6f6a4
Show file tree
Hide file tree
Showing 9 changed files with 311 additions and 3 deletions.
71 changes: 69 additions & 2 deletions src/NRedisStack/Auxiliary.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
using System.Xml.Linq;
using NRedisStack.Core;
using NRedisStack.RedisStackCommands;
using StackExchange.Redis;

namespace NRedisStack
{
public static class Auxiliary
{
private static string? _libraryName = $"NRedisStack;.NET-{Environment.Version}";
private static bool _setInfo = true;
public static void ResetInfoDefaults()
{
_setInfo = true;
_libraryName = $"NRedisStack;.NET-{Environment.Version}";
}
public static List<object> MergeArgs(RedisKey key, params RedisValue[] items)
{
var args = new List<object>(items.Length + 1) { key };
Expand All @@ -26,20 +35,57 @@ public static object[] AssembleNonNullArguments(params object?[] arguments)
return args.ToArray();
}

// public static IDatabase GetDatabase(this ConnectionMultiplexer redis) => redis.GetDatabase("", "");

// TODO: add all the signatures of GetDatabase
public static IDatabase GetDatabase(this ConnectionMultiplexer redis,
string? LibraryName)
{
var _db = redis.GetDatabase();
if (LibraryName == null) // the user wants to disable the library name and version sending
_setInfo = false;

else // the user set his own the library name
_libraryName = $"NRedisStack({LibraryName});.NET-{Environment.Version})";

return _db;
}

private static void SetInfoInPipeline(this IDatabase db)
{
if (_libraryName == null) return;
Pipeline pipeline = new Pipeline(db);
_ = pipeline.Db.ClientSetInfoAsync(SetInfoAttr.LibraryName, _libraryName!);
_ = pipeline.Db.ClientSetInfoAsync(SetInfoAttr.LibraryVersion, GetNRedisStackVersion());
pipeline.Execute();
}

public static RedisResult Execute(this IDatabase db, SerializedCommand command)
{
var compareVersions = db.Multiplexer.GetServer(db.Multiplexer.GetEndPoints()[0]).Version.CompareTo(new Version(7, 1, 242));
if (_setInfo && compareVersions >= 0)
{
_setInfo = false;
db.SetInfoInPipeline();
}
return db.Execute(command.Command, command.Args);
}

public async static Task<RedisResult> ExecuteAsync(this IDatabaseAsync db, SerializedCommand command)
{
var compareVersions = db.Multiplexer.GetServer(db.Multiplexer.GetEndPoints()[0]).Version.CompareTo(new Version(7, 1, 242));
if (_setInfo && compareVersions >= 0)
{
_setInfo = false;
((IDatabase)db).SetInfoInPipeline();
}
return await db.ExecuteAsync(command.Command, command.Args);
}

public static List<RedisResult> ExecuteBroadcast(this IDatabaseAsync db, string command)
public static List<RedisResult> ExecuteBroadcast(this IDatabase db, string command)
=> db.ExecuteBroadcast(new SerializedCommand(command));

public static List<RedisResult> ExecuteBroadcast(this IDatabaseAsync db, SerializedCommand command)
public static List<RedisResult> ExecuteBroadcast(this IDatabase db, SerializedCommand command)
{
var redis = db.Multiplexer;
var endpoints = redis.GetEndPoints();
Expand Down Expand Up @@ -83,5 +129,26 @@ public async static Task<List<RedisResult>> ExecuteBroadcastAsync(this IDatabase
}
return results;
}

public static string GetNRedisStackVersion()
{
XDocument csprojDocument = GetCsprojDocument();

// Find the Version element and get its value.
var versionElement = csprojDocument.Root!
.Descendants("Version")
.FirstOrDefault();

return versionElement!.Value;
}

private static XDocument GetCsprojDocument()
{
string csprojFilePath = Path.Combine(Directory.GetCurrentDirectory(), "..", "..", "..", "..", "..", "src", "NRedisStack", "NRedisStack.csproj");

// Load the .csproj file.
var csprojDocument = XDocument.Load(csprojFilePath);
return csprojDocument;
}
}
}
22 changes: 22 additions & 0 deletions src/NRedisStack/CoreCommands/CoreCommandBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using NRedisStack.RedisStackCommands;
using NRedisStack.Core.Literals;
using NRedisStack.Core;

namespace NRedisStack
{

public static class CoreCommandBuilder
{
public static SerializedCommand ClientSetInfo(SetInfoAttr attr, string value)
{
string attrValue = attr switch
{
SetInfoAttr.LibraryName => CoreArgs.lib_name,
SetInfoAttr.LibraryVersion => CoreArgs.lib_ver,
_ => throw new System.NotImplementedException(),
};

return new SerializedCommand(RedisCoreCommands.CLIENT, RedisCoreCommands.SETINFO, attrValue, value);
}
}
}
20 changes: 20 additions & 0 deletions src/NRedisStack/CoreCommands/CoreCommands.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using NRedisStack.Core;
using StackExchange.Redis;
namespace NRedisStack
{

public static class CoreCommands
{
/// <summary>
/// Sets information specific to the client or connection.
/// </summary>
/// <param name="attr">which attribute to set</param>
/// <param name="value">the attribute value</param>
/// <returns><see langword="true"/> if the attribute name was successfully set, Error otherwise.</returns>
/// <remarks><seealso href="https://redis.io/commands/client-setinfo/"/></remarks>
public static bool ClientSetInfo(this IDatabase db, SetInfoAttr attr, string value)
{
return db.Execute(CoreCommandBuilder.ClientSetInfo(attr, value)).OKtoBoolean();
}
}
}
20 changes: 20 additions & 0 deletions src/NRedisStack/CoreCommands/CoreCommandsAsync.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using NRedisStack.Core;
using StackExchange.Redis;
namespace NRedisStack
{

public static class CoreCommandsAsync //: ICoreCommandsAsync
{
/// <summary>
/// Sets information specific to the client or connection.
/// </summary>
/// <param name="attr">which attribute to set</param>
/// <param name="value">the attribute value</param>
/// <returns><see langword="true"/> if the attribute name was successfully set, Error otherwise.</returns>
/// <remarks><seealso href="https://redis.io/commands/client-setinfo/"/></remarks>
public static async Task<bool> ClientSetInfoAsync(this IDatabaseAsync db, SetInfoAttr attr, string value)
{
return (await db.ExecuteAsync(CoreCommandBuilder.ClientSetInfo(attr, value))).OKtoBoolean();
}
}
}
12 changes: 12 additions & 0 deletions src/NRedisStack/CoreCommands/Enums/SetInfoAttr.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace NRedisStack.Core;
public enum SetInfoAttr
{
/// <summary>
/// meant to hold the name of the client library that's in use.
/// </summary>
LibraryName,
/// <summary>
/// meant to hold the client library's version.
/// </summary>
LibraryVersion
}
8 changes: 8 additions & 0 deletions src/NRedisStack/CoreCommands/Literals/CommandArgs.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace NRedisStack.Core.Literals
{
internal class CoreArgs
{
public const string lib_name = "LIB-NAME";
public const string lib_ver = "LIB-VER";
}
}
11 changes: 11 additions & 0 deletions src/NRedisStack/CoreCommands/Literals/Commands.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace NRedisStack.Core.Literals
{
/// <summary>
/// Redis Core command literals
/// </summary>
internal class RedisCoreCommands
{
public const string CLIENT = "CLIENT";
public const string SETINFO = "SETINFO";
}
}
1 change: 0 additions & 1 deletion src/NRedisStack/Graph/IGraphCommands.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

namespace NRedisStack
{

[Obsolete("RedisGraph support is deprecated as of Redis Stack 7.2 (https://redis.com/blog/redisgraph-eol/)")]
public interface IGraphCommands
{
Expand Down
149 changes: 149 additions & 0 deletions tests/NRedisStack.Tests/Core Commands/CoreTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
using Xunit;
using NRedisStack.Core;
using NRedisStack;
using static NRedisStack.Auxiliary;
using StackExchange.Redis;
using System.Xml.Linq;
using System.Reflection;
using NRedisStack.RedisStackCommands;


namespace NRedisStack.Tests.Core;

public class CoreTests : AbstractNRedisStackTest, IDisposable
{
public CoreTests(RedisFixture redisFixture) : base(redisFixture) { }


[SkipIfRedis(Is.OSSCluster, Comparison.LessThan, "7.1.242")]
public void TestSimpleSetInfo()
{
var redis = ConnectionMultiplexer.Connect("localhost");
var db = redis.GetDatabase();
db.Execute("FLUSHALL");

db.ClientSetInfo(SetInfoAttr.LibraryName, "TestLibraryName");
db.ClientSetInfo(SetInfoAttr.LibraryVersion, "1.2.3");

var info = db.Execute("CLIENT", "INFO").ToString();
Assert.EndsWith($"lib-name=TestLibraryName lib-ver=1.2.3\n", info);
}

[SkipIfRedis(Is.OSSCluster, Comparison.LessThan, "7.1.242")]
public async Task TestSimpleSetInfoAsync()
{
var redis = ConnectionMultiplexer.Connect("localhost");
var db = redis.GetDatabase();
db.Execute("FLUSHALL");

await db.ClientSetInfoAsync(SetInfoAttr.LibraryName, "TestLibraryName");
await db.ClientSetInfoAsync(SetInfoAttr.LibraryVersion, "1.2.3");

var info = db.Execute("CLIENT", "INFO").ToString();
Assert.EndsWith($"lib-name=TestLibraryName lib-ver=1.2.3\n", info);
}

[SkipIfRedis(Is.OSSCluster, Comparison.LessThan, "7.1.242")]
public void TestSetInfoDefaultValue()
{
ResetInfoDefaults(); // demonstrate first connection
var redis = ConnectionMultiplexer.Connect("localhost");
var db = redis.GetDatabase();
db.Execute("FLUSHALL");

db.Execute(new SerializedCommand("PING")); // only the extension method of Execute (which is used for all the commands of Redis Stack) will set the library name and version.

var info = db.Execute("CLIENT", "INFO").ToString();
Assert.EndsWith($"lib-name=NRedisStack;.NET-{Environment.Version} lib-ver={GetNRedisStackVersion()}\n", info);
}

[SkipIfRedis(Is.OSSCluster, Comparison.LessThan, "7.1.242")]
public async Task TestSetInfoDefaultValueAsync()
{
ResetInfoDefaults(); // demonstrate first connection
var redis = ConnectionMultiplexer.Connect("localhost");
var db = redis.GetDatabase();
db.Execute("FLUSHALL");

await db.ExecuteAsync(new SerializedCommand("PING")); // only the extension method of Execute (which is used for all the commands of Redis Stack) will set the library name and version.

var info = (await db.ExecuteAsync("CLIENT", "INFO")).ToString();
Assert.EndsWith($"lib-name=NRedisStack;.NET-{Environment.Version} lib-ver={GetNRedisStackVersion()}\n", info);
}

[SkipIfRedis(Is.OSSCluster, Comparison.LessThan, "7.1.242")]
public void TestSetInfoWithValue()
{
ResetInfoDefaults(); // demonstrate first connection
var redis = ConnectionMultiplexer.Connect("localhost");
var db = redis.GetDatabase("MyLibraryName;v1.0.0");
db.Execute("FLUSHALL");

db.Execute(new SerializedCommand("PING")); // only the extension method of Execute (which is used for all the commands of Redis Stack) will set the library name and version.

var info = db.Execute("CLIENT", "INFO").ToString();
Assert.EndsWith($"NRedisStack(MyLibraryName;v1.0.0);.NET-{Environment.Version}) lib-ver={GetNRedisStackVersion()}\n", info);
}

[SkipIfRedis(Is.OSSCluster, Comparison.LessThan, "7.1.242")]
public async Task TestSetInfoWithValueAsync()
{
ResetInfoDefaults(); // demonstrate first connection
var redis = ConnectionMultiplexer.Connect("localhost");
var db = redis.GetDatabase("MyLibraryName;v1.0.0");
db.Execute("FLUSHALL");

await db.ExecuteAsync(new SerializedCommand("PING")); // only the extension method of Execute (which is used for all the commands of Redis Stack) will set the library name and version.

var info = (await db.ExecuteAsync("CLIENT", "INFO")).ToString();
Assert.EndsWith($"NRedisStack(MyLibraryName;v1.0.0);.NET-{Environment.Version}) lib-ver={GetNRedisStackVersion()}\n", info);
}

[SkipIfRedis(Is.OSSCluster, Comparison.LessThan, "7.1.242")]
public void TestSetInfoNull()
{
ResetInfoDefaults(); // demonstrate first connection
var redis = ConnectionMultiplexer.Connect("localhost");
var db = redis.GetDatabase(null);

db.Execute("FLUSHALL");
var infoBefore = db.Execute("CLIENT", "INFO").ToString();
db.Execute(new SerializedCommand("PING")); // only the extension method of Execute (which is used for all the commands of Redis Stack) will set the library name and version.

var infoAfter = db.Execute("CLIENT", "INFO").ToString();
// Find the indices of "lib-name=" in the strings
int infoAfterLibNameIndex = infoAfter!.IndexOf("lib-name=");
int infoBeforeLibNameIndex = infoBefore!.IndexOf("lib-name=");

// Extract the sub-strings starting from "lib-name="
string infoAfterLibNameToEnd = infoAfter.Substring(infoAfterLibNameIndex);
string infoBeforeLibNameToEnd = infoBefore.Substring(infoBeforeLibNameIndex);

// Assert that the extracted sub-strings are equal
Assert.Equal(infoAfterLibNameToEnd, infoBeforeLibNameToEnd);
}

[SkipIfRedis(Is.OSSCluster, Comparison.LessThan, "7.1.242")]
public async Task TestSetInfoNullAsync()
{
ResetInfoDefaults(); // demonstrate first connection
var redis = ConnectionMultiplexer.Connect("localhost");
var db = redis.GetDatabase(null);

db.Execute("FLUSHALL");
var infoBefore = (await db.ExecuteAsync("CLIENT", "INFO")).ToString();
await db.ExecuteAsync(new SerializedCommand("PING")); // only the extension method of Execute (which is used for all the commands of Redis Stack) will set the library name and version.

var infoAfter = (await db.ExecuteAsync("CLIENT", "INFO")).ToString();
// Find the indices of "lib-name=" in the strings
int infoAfterLibNameIndex = infoAfter!.IndexOf("lib-name=");
int infoBeforeLibNameIndex = infoBefore!.IndexOf("lib-name=");

// Extract the sub-strings starting from "lib-name="
string infoAfterLibNameToEnd = infoAfter.Substring(infoAfterLibNameIndex);
string infoBeforeLibNameToEnd = infoBefore.Substring(infoBeforeLibNameIndex);

// Assert that the extracted sub-strings are equal
Assert.Equal(infoAfterLibNameToEnd, infoBeforeLibNameToEnd);
}
}

0 comments on commit aa6f6a4

Please sign in to comment.