Skip to content

Commit c4b5f22

Browse files
committed
Recording redis version in the executor
1 parent 9a23717 commit c4b5f22

3 files changed

Lines changed: 157 additions & 5 deletions

File tree

src/VirtualClient/VirtualClient.Actions.FunctionalTests/Redis/RedisServerProfileTests.cs

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ namespace VirtualClient.Actions
88
using System.Linq;
99
using System.Net;
1010
using System.Runtime.InteropServices;
11+
using System.Text;
1112
using System.Threading;
1213
using System.Threading.Tasks;
1314
using Moq;
@@ -21,7 +22,7 @@ namespace VirtualClient.Actions
2122
public class RedisServerProfileTests
2223
{
2324
private DependencyFixture mockFixture;
24-
25+
2526
[SetUp]
2627
public void SetupFixture()
2728
{
@@ -44,6 +45,24 @@ public void SetupFixture()
4445
[TestCase("PERF-REDIS.json")]
4546
public async Task RedisMemtierWorkloadProfileInstallsTheExpectedDependenciesOfServerOnUnixPlatform(string profile)
4647
{
48+
using var memoryProcess = new InMemoryProcess
49+
{
50+
StandardOutput = new ConcurrentBuffer(
51+
new StringBuilder("Redis server v=7.0.15 sha=00000000 malloc=jemalloc-5.1.0 bits=64 build=abc123")),
52+
OnStart = () => true,
53+
OnHasExited = () => true
54+
};
55+
56+
this.mockFixture.ProcessManager.OnCreateProcess = (command, arguments, workingDir) =>
57+
{
58+
IProcessProxy process = this.mockFixture.CreateProcess(command, arguments, workingDir);
59+
if (arguments?.Contains("redis-server") == true && arguments?.Contains("--version") == true)
60+
{
61+
return memoryProcess;
62+
}
63+
64+
return process;
65+
};
4766
using (ProfileExecutor executor = TestDependencies.CreateProfileExecutor(profile, this.mockFixture.Dependencies))
4867
{
4968
await executor.ExecuteAsync(ProfileTiming.OneIteration(), CancellationToken.None)
@@ -86,10 +105,21 @@ public async Task RedisMemtierWorkloadProfileExecutesTheWorkloadAsExpectedOfServ
86105
});
87106

88107
await apiClient.CreateStateAsync(nameof(ServerState), state, CancellationToken.None);
108+
using var memoryProcess = new InMemoryProcess
109+
{
110+
StandardOutput = new ConcurrentBuffer(
111+
new StringBuilder("Redis server v=7.0.15 sha=00000000 malloc=jemalloc-5.1.0 bits=64 build=abc123")),
112+
OnStart = () => true,
113+
OnHasExited = () => true
114+
};
89115

90116
this.mockFixture.ProcessManager.OnCreateProcess = (command, arguments, workingDir) =>
91117
{
92118
IProcessProxy process = this.mockFixture.CreateProcess(command, arguments, workingDir);
119+
if (arguments?.Contains("redis-server") == true && arguments?.Contains("--version") == true)
120+
{
121+
return memoryProcess;
122+
}
93123

94124
return process;
95125
};

src/VirtualClient/VirtualClient.Actions.UnitTests/Redis/RedisServerExecutorTests.cs

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,16 @@ namespace VirtualClient.Actions
55
{
66
using System;
77
using System.Collections.Generic;
8+
using System.Linq;
89
using System.Net;
10+
using System.Text;
911
using System.Threading;
1012
using System.Threading.Tasks;
1113
using Microsoft.Extensions.DependencyInjection;
1214
using Moq;
1315
using NUnit.Framework;
1416
using VirtualClient.Actions.Memtier;
17+
using VirtualClient.Common;
1518
using VirtualClient.Common.Telemetry;
1619
using VirtualClient.Contracts;
1720

@@ -21,13 +24,19 @@ public class RedisServerExecutorTests
2124
{
2225
private MockFixture fixture;
2326
private DependencyPath mockRedisPackage;
27+
private InMemoryProcess memoryProcess;
2428

2529
[SetUp]
2630
public void SetupTests()
2731
{
2832
this.fixture = new MockFixture();
2933
this.fixture.Setup(PlatformID.Unix);
30-
34+
this.memoryProcess = new InMemoryProcess
35+
{
36+
ExitCode = 0,
37+
OnStart = () => true,
38+
OnHasExited = () => true
39+
};
3140
this.mockRedisPackage = new DependencyPath("redis", this.fixture.GetPackagePath("redis"));
3241

3342
this.fixture.Parameters = new Dictionary<string, IConvertible>()
@@ -62,6 +71,17 @@ public async Task RedisServerExecutorConfirmsTheExpectedPackagesOnInitialization
6271
{
6372
using (var component = new TestRedisServerExecutor(this.fixture.Dependencies, this.fixture.Parameters))
6473
{
74+
this.fixture.ProcessManager.OnCreateProcess = (command, arguments, workingDirectory) =>
75+
{
76+
if (arguments?.Contains("redis-server") == true && arguments?.Contains("--version") == true)
77+
{
78+
this.memoryProcess.StandardOutput = new ConcurrentBuffer(
79+
new StringBuilder("Redis server v=7.0.15 sha=00000000 malloc=jemalloc-5.1.0 bits=64 build=abc123")
80+
);
81+
return this.memoryProcess;
82+
}
83+
return this.memoryProcess;
84+
};
6585
await component.InitializeAsync(EventContext.None, CancellationToken.None);
6686
this.fixture.PackageManager.Verify(mgr => mgr.GetPackageAsync(this.mockRedisPackage.Name, It.IsAny<CancellationToken>()));
6787
}
@@ -83,6 +103,13 @@ public async Task RedisMemtierServerExecutorExecutesExpectedProcessWhenBindingTo
83103

84104
this.fixture.ProcessManager.OnCreateProcess = (exe, arguments, workingDirectory) =>
85105
{
106+
if (arguments?.Contains("redis-server") == true && arguments?.Contains("--version") == true)
107+
{
108+
this.memoryProcess.StandardOutput = new ConcurrentBuffer(
109+
new StringBuilder("Redis server v=7.0.15 sha=00000000 malloc=jemalloc-5.1.0 bits=64 build=abc123")
110+
);
111+
return this.memoryProcess;
112+
}
86113
expectedCommands.Remove($"{exe} {arguments}");
87114
return this.fixture.Process;
88115
};
@@ -113,6 +140,13 @@ public async Task RedisMemtierServerExecutorExecutesExpectedProcessWhenBindingTo
113140

114141
this.fixture.ProcessManager.OnCreateProcess = (exe, arguments, workingDirectory) =>
115142
{
143+
if (arguments?.Contains("redis-server") == true && arguments?.Contains("--version") == true)
144+
{
145+
this.memoryProcess.StandardOutput = new ConcurrentBuffer(
146+
new StringBuilder("Redis server v=7.0.15 sha=00000000 malloc=jemalloc-5.1.0 bits=64 build=abc123")
147+
);
148+
return this.memoryProcess;
149+
}
116150
expectedCommands.Remove($"{exe} {arguments}");
117151
return this.fixture.Process;
118152
};
@@ -140,6 +174,13 @@ public async Task RedisMemtierServerExecutorExecutesExpectedProcessWhenNotBindin
140174

141175
this.fixture.ProcessManager.OnCreateProcess = (exe, arguments, workingDirectory) =>
142176
{
177+
if (arguments?.Contains("redis-server") == true && arguments?.Contains("--version") == true)
178+
{
179+
this.memoryProcess.StandardOutput = new ConcurrentBuffer(
180+
new StringBuilder("Redis server v=7.0.15 sha=00000000 malloc=jemalloc-5.1.0 bits=64 build=abc123")
181+
);
182+
return this.memoryProcess;
183+
}
143184
expectedCommands.Remove($"{exe} {arguments}");
144185
return this.fixture.Process;
145186
};
@@ -149,6 +190,38 @@ public async Task RedisMemtierServerExecutorExecutesExpectedProcessWhenNotBindin
149190
}
150191
}
151192

193+
[Test]
194+
public async Task RedisServerExecutorCapturesRedisVersionSuccessfully()
195+
{
196+
using (var executor = new TestRedisServerExecutor(this.fixture.Dependencies, this.fixture.Parameters))
197+
{
198+
this.fixture.ProcessManager.OnCreateProcess = (command, arguments, workingDirectory) =>
199+
{
200+
if (arguments?.Contains("redis-server") == true && arguments?.Contains("--version") == true)
201+
{
202+
this.memoryProcess.StandardOutput = new ConcurrentBuffer(
203+
new StringBuilder("Redis server v=7.0.15 sha=00000000 malloc=jemalloc-5.1.0 bits=64 build=abc123")
204+
);
205+
return this.memoryProcess;
206+
}
207+
return this.memoryProcess;
208+
};
209+
// Act
210+
await executor.InitializeAsync(EventContext.None, CancellationToken.None);
211+
// Assert
212+
var messages = this.fixture.Logger.MessagesLogged($"{nameof(TestRedisServerExecutor)}.RedisVersionCaptured");
213+
Assert.IsNotEmpty(messages, "Expected at least one log message indicating the Redis version was captured.");
214+
bool versionCapturedCorrectly = messages.Any(msg =>
215+
{
216+
var eventContext = msg.Item3 as EventContext;
217+
return eventContext != null &&
218+
eventContext.Properties.ContainsKey("redisVersion") &&
219+
eventContext.Properties["redisVersion"].ToString() == "7.0.15";
220+
});
221+
Assert.IsTrue(versionCapturedCorrectly, "The Redis version '7.0.15' was not captured correctly in the logs.");
222+
}
223+
}
224+
152225
private class TestRedisServerExecutor : RedisServerExecutor
153226
{
154227
public TestRedisServerExecutor(IServiceCollection services, IDictionary<string, IConvertible> parameters = null)

src/VirtualClient/VirtualClient.Actions/Redis/RedisServerExecutor.cs

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@ namespace VirtualClient.Actions
55
{
66
using System;
77
using System.Collections.Generic;
8+
using System.IO;
89
using System.Linq;
910
using System.Net;
1011
using System.Net.Http;
12+
using System.Text.RegularExpressions;
1113
using System.Threading;
1214
using System.Threading.Tasks;
1315
using Microsoft.Extensions.DependencyInjection;
@@ -19,6 +21,7 @@ namespace VirtualClient.Actions
1921
using VirtualClient.Common.Extensions;
2022
using VirtualClient.Common.Telemetry;
2123
using VirtualClient.Contracts;
24+
using VirtualClient.Contracts.Metadata;
2225
using VirtualClient.Logging;
2326

2427
/// <summary>
@@ -142,6 +145,8 @@ protected IApiClient ApiClient
142145
/// </summary>
143146
protected IAsyncPolicy ServerRetryPolicy { get; set; }
144147

148+
private string RedisVersion { get; set; }
149+
145150
/// <summary>
146151
/// Disposes of resources used by the executor including shutting down any
147152
/// instances of Redis server running.
@@ -195,7 +200,6 @@ protected override Task ExecuteAsync(EventContext telemetryContext, Cancellation
195200

196201
await this.SaveStateAsync(telemetryContext, cancellationToken);
197202
this.SetServerOnline(true);
198-
199203
if (this.IsMultiRoleLayout())
200204
{
201205
using (BackgroundOperations profiling = BackgroundOperations.BeginProfiling(this, cancellationToken))
@@ -237,13 +241,13 @@ protected override async Task InitializeAsync(EventContext telemetryContext, Can
237241
this.RedisExecutablePath = this.Combine(this.RedisPackagePath, "src", "redis-server");
238242

239243
await this.SystemManagement.MakeFileExecutableAsync(this.RedisExecutablePath, this.Platform, cancellationToken);
240-
244+
await this.CaptureRedisVersionAsync(telemetryContext, cancellationToken);
241245
if (this.IsTLSEnabled)
242246
{
243247
DependencyPath redisResourcesPath = await this.GetPackageAsync(this.RedisResourcesPackageName, cancellationToken);
244248
this.RedisResourcesPath = redisResourcesPath.Path;
245249
}
246-
250+
247251
this.InitializeApiClients();
248252
}
249253

@@ -264,6 +268,51 @@ protected override void Validate()
264268
}
265269
}
266270

271+
private async Task CaptureRedisVersionAsync(EventContext telemetryContext, CancellationToken token)
272+
{
273+
this.EnsureRedisServerInRootPackagePath(telemetryContext);
274+
string redisVersionPath = this.Combine(this.RedisPackagePath, "redis-server");
275+
string command = $"{redisVersionPath} --version";
276+
IProcessProxy process = await this.ExecuteCommandAsync(command, null, this.RedisPackagePath, telemetryContext, token, runElevated: true);
277+
string output = process.StandardOutput.ToString();
278+
Match match = Regex.Match(output, @"v=(\d+\.\d+\.\d+)");
279+
if (match.Success)
280+
{
281+
this.RedisVersion = match.Groups[1].Value;
282+
telemetryContext.AddContext("RedisVersion", this.RedisVersion);
283+
this.Logger.LogMessage($"{this.TypeName}.RedisVersionCaptured", LogLevel.Information, telemetryContext);
284+
this.MetadataContract.AddForScenario(
285+
"Redis-Benchmark",
286+
null,
287+
toolVersion: this.RedisVersion);
288+
this.MetadataContract.Apply(telemetryContext);
289+
}
290+
else
291+
{
292+
throw new WorkloadException("Failed to parse Redis version from output.", ErrorReason.CriticalWorkloadFailure);
293+
}
294+
}
295+
296+
private void EnsureRedisServerInRootPackagePath(EventContext telemetryContext)
297+
{
298+
string packagePath = this.RedisPackagePath;
299+
string srcDir = Path.Combine(packagePath, "src");
300+
string redisServerSrc = Path.Combine(srcDir, "redis-server");
301+
string redisServerDst = Path.Combine(packagePath, "redis-server");
302+
try
303+
{
304+
if (File.Exists(redisServerSrc) && !File.Exists(redisServerDst))
305+
{
306+
File.Copy(redisServerSrc, redisServerDst, overwrite: true);
307+
this.Logger.LogMessage($"{this.TypeName}.RedisServerBinaryCopied", LogLevel.Debug, telemetryContext.Clone().AddContext("copiedFrom", redisServerSrc).AddContext("copiedTo", redisServerDst));
308+
}
309+
}
310+
catch (Exception ex)
311+
{
312+
this.Logger.LogMessage($"{this.TypeName}.RedisBinaryCopyError", LogLevel.Warning, telemetryContext.Clone().AddError(ex).AddContext("source", redisServerSrc));
313+
}
314+
}
315+
267316
private Task DeleteStateAsync(EventContext telemetryContext, CancellationToken cancellationToken)
268317
{
269318
EventContext relatedContext = telemetryContext.Clone();

0 commit comments

Comments
 (0)