From c8e9d168058799ee97e7e302a1497c115c3b23a6 Mon Sep 17 00:00:00 2001 From: Erica Vellanoweth Date: Wed, 19 Aug 2026 16:38:36 -0700 Subject: [PATCH 1/3] Fix MySQL sysbench population failures Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 92224b9d-2c89-489f-aff7-a16fae94d4cf --- .../Sysbench/SysbenchConfigurationTests.cs | 35 +++++++++++++++++++ .../Sysbench/SysbenchConfiguration.cs | 8 +++++ .../profiles/PERF-MYSQL-SYSBENCH-OLTP.json | 2 +- .../profiles/PERF-MYSQL-SYSBENCH-TPCC.json | 2 +- 4 files changed, 45 insertions(+), 2 deletions(-) diff --git a/src/VirtualClient/VirtualClient.Actions.UnitTests/Sysbench/SysbenchConfigurationTests.cs b/src/VirtualClient/VirtualClient.Actions.UnitTests/Sysbench/SysbenchConfigurationTests.cs index f7ae18a06d..b085d0509c 100644 --- a/src/VirtualClient/VirtualClient.Actions.UnitTests/Sysbench/SysbenchConfigurationTests.cs +++ b/src/VirtualClient/VirtualClient.Actions.UnitTests/Sysbench/SysbenchConfigurationTests.cs @@ -21,6 +21,7 @@ namespace VirtualClient.Actions using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; + using VirtualClient.Common; using VirtualClient.Common.Contracts; using VirtualClient.Common.Telemetry; using VirtualClient.Contracts; @@ -163,6 +164,40 @@ public async Task SysbenchConfigurationPreparesDatabase() } } + [Test] + public void SysbenchConfigurationThrowsWhenPopulationReportsFatalError() + { + this.fixture.StateManager.OnGetState().ReturnsAsync(JObject.FromObject(new SysbenchExecutor.SysbenchState() + { + SysbenchInitialized = true + })); + + this.fixture.ProcessManager.OnCreateProcess = (exe, arguments, workingDir) => + { + return new InMemoryProcess + { + StartInfo = new ProcessStartInfo + { + FileName = exe, + Arguments = arguments + }, + ExitCode = 0, + OnStart = () => true, + OnHasExited = () => true, + StandardOutput = new ConcurrentBuffer(new StringBuilder( + "FATAL: error 1130: Host '10.0.1.0' is not allowed to connect to this MySQL server")) + }; + }; + + using (TestSysbenchConfiguration sysbenchConfiguration = new TestSysbenchConfiguration(this.fixture.Dependencies, this.fixture.Parameters)) + { + WorkloadException error = Assert.ThrowsAsync( + () => sysbenchConfiguration.ExecuteAsync(CancellationToken.None)); + + Assert.AreEqual(ErrorReason.WorkloadUnexpectedAnomaly, error.Reason); + } + } + [Test] public async Task SysbenchConfigurationUsesDefinedParametersWhenRunningTheWorkload() { diff --git a/src/VirtualClient/VirtualClient.Actions/Sysbench/SysbenchConfiguration.cs b/src/VirtualClient/VirtualClient.Actions/Sysbench/SysbenchConfiguration.cs index 5ebaa198b5..41f973d097 100644 --- a/src/VirtualClient/VirtualClient.Actions/Sysbench/SysbenchConfiguration.cs +++ b/src/VirtualClient/VirtualClient.Actions/Sysbench/SysbenchConfiguration.cs @@ -131,6 +131,14 @@ await this.Logger.LogMessageAsync($"{this.TypeName}.PopulateDatabase", telemetry await this.LogProcessDetailsAsync(process, telemetryContext, "Sysbench", logToFile: true); process.ThrowIfErrored(process.StandardError.ToString(), ErrorReason.WorkloadUnexpectedAnomaly); + if (process.StandardOutput.ToString().Contains("FATAL:", StringComparison.OrdinalIgnoreCase) + || process.StandardError.ToString().Contains("FATAL:", StringComparison.OrdinalIgnoreCase)) + { + throw new WorkloadException( + "Sysbench reported a fatal error while populating the database.", + ErrorReason.WorkloadUnexpectedAnomaly); + } + this.AddPopulationDurationMetric(sysbenchLoggingArguments, process, telemetryContext, cancellationToken); state.DatabasePopulated = true; diff --git a/src/VirtualClient/VirtualClient.Main/profiles/PERF-MYSQL-SYSBENCH-OLTP.json b/src/VirtualClient/VirtualClient.Main/profiles/PERF-MYSQL-SYSBENCH-OLTP.json index 27cd86e1df..80891e175a 100644 --- a/src/VirtualClient/VirtualClient.Main/profiles/PERF-MYSQL-SYSBENCH-OLTP.json +++ b/src/VirtualClient/VirtualClient.Main/profiles/PERF-MYSQL-SYSBENCH-OLTP.json @@ -182,7 +182,7 @@ "Parameters": { "Scenario": "DownloadMySqlServerPackage", "BlobContainer": "packages", - "BlobName": "mysql-server-8.0.36-v5.zip", + "BlobName": "mysql-server-8.0.46-v1.zip", "PackageName": "mysql-server", "Extract": true, "Role": "Server" diff --git a/src/VirtualClient/VirtualClient.Main/profiles/PERF-MYSQL-SYSBENCH-TPCC.json b/src/VirtualClient/VirtualClient.Main/profiles/PERF-MYSQL-SYSBENCH-TPCC.json index b23b59b725..d83d03843c 100644 --- a/src/VirtualClient/VirtualClient.Main/profiles/PERF-MYSQL-SYSBENCH-TPCC.json +++ b/src/VirtualClient/VirtualClient.Main/profiles/PERF-MYSQL-SYSBENCH-TPCC.json @@ -70,7 +70,7 @@ "Parameters": { "Scenario": "DownloadMySqlServerPackage", "BlobContainer": "packages", - "BlobName": "mysql-server-8.0.36-v5.zip", + "BlobName": "mysql-server-8.0.46-v1.zip", "PackageName": "mysql-server", "Extract": true, "Role": "Server" From 6a4ad88264ba79e567f3979bf7e47286e5bd743b Mon Sep 17 00:00:00 2001 From: Erica Vellanoweth Date: Wed, 19 Aug 2026 16:51:40 -0700 Subject: [PATCH 2/3] Synchronize MySQL database population Wait for the server API heartbeat before a client-role population step. The API server starts after MySQL configuration and user creation, preventing the first thread-sweep scenario from racing the remote user grant. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 92224b9d-2c89-489f-aff7-a16fae94d4cf --- .../Sysbench/SysbenchConfigurationTests.cs | 44 +++++++++++++++++++ .../Sysbench/SysbenchConfiguration.cs | 14 ++++++ 2 files changed, 58 insertions(+) diff --git a/src/VirtualClient/VirtualClient.Actions.UnitTests/Sysbench/SysbenchConfigurationTests.cs b/src/VirtualClient/VirtualClient.Actions.UnitTests/Sysbench/SysbenchConfigurationTests.cs index b085d0509c..666829b907 100644 --- a/src/VirtualClient/VirtualClient.Actions.UnitTests/Sysbench/SysbenchConfigurationTests.cs +++ b/src/VirtualClient/VirtualClient.Actions.UnitTests/Sysbench/SysbenchConfigurationTests.cs @@ -118,6 +118,50 @@ public async Task SysbenchConfigurationSkipsSysbenchInitialization() } } + [Test] + public async Task SysbenchConfigurationWaitsForServerDependenciesBeforeRemotePopulation() + { + this.fixture.StateManager.OnGetState().ReturnsAsync(JObject.FromObject(new SysbenchExecutor.SysbenchState() + { + SysbenchInitialized = true + })); + + bool serverHeartbeatConfirmed = false; + this.fixture.ApiClient.OnGetHeartbeat().ReturnsAsync(() => + { + serverHeartbeatConfirmed = true; + return this.fixture.CreateHttpResponse(HttpStatusCode.OK); + }); + + this.fixture.ProcessManager.OnCreateProcess = (exe, arguments, workingDir) => + { + Assert.IsTrue(serverHeartbeatConfirmed); + + return new InMemoryProcess + { + StartInfo = new ProcessStartInfo + { + FileName = exe, + Arguments = arguments + }, + ExitCode = 0, + OnStart = () => true, + OnHasExited = () => true + }; + }; + + using (TestSysbenchConfiguration sysbenchConfiguration = new TestSysbenchConfiguration(this.fixture.Dependencies, this.fixture.Parameters)) + { + await sysbenchConfiguration.ExecuteAsync(CancellationToken.None); + } + + this.fixture.ApiClient.Verify( + client => client.GetHeartbeatAsync( + It.IsAny(), + It.IsAny>()), + Times.Once); + } + [Test] public async Task SysbenchConfigurationPreparesDatabase() { diff --git a/src/VirtualClient/VirtualClient.Actions/Sysbench/SysbenchConfiguration.cs b/src/VirtualClient/VirtualClient.Actions/Sysbench/SysbenchConfiguration.cs index 41f973d097..c7eda1471c 100644 --- a/src/VirtualClient/VirtualClient.Actions/Sysbench/SysbenchConfiguration.cs +++ b/src/VirtualClient/VirtualClient.Actions/Sysbench/SysbenchConfiguration.cs @@ -31,6 +31,7 @@ public SysbenchConfiguration(IServiceCollection dependencies, IDictionary(); + this.PollingTimeout = TimeSpan.FromMinutes(10); } /// @@ -45,6 +46,11 @@ public string Action } } + /// + /// The amount of time to wait for the server dependencies to complete. + /// + protected TimeSpan PollingTimeout { get; set; } + /// /// Executes the workload. /// @@ -110,6 +116,14 @@ private async Task PopulateDatabase(EventContext telemetryContext, CancellationT if (!state.DatabasePopulated) { + if (this.IsMultiRoleLayout() && this.IsInRole(ClientRole.Client)) + { + this.Logger.LogTraceMessage("Synchronization: Poll server API for heartbeat before database population..."); + + await this.ServerApiClient.PollForHeartbeatAsync(this.PollingTimeout, cancellationToken) + .ConfigureAwait(false); + } + await this.Logger.LogMessageAsync($"{this.TypeName}.PopulateDatabase", telemetryContext.Clone(), async () => { string serverIp = (this.IsMultiRoleLayout() && this.IsInRole(ClientRole.Client)) ? this.ServerIpAddress : "localhost"; From 165e7c6f47987b37e815c1f6fa5ad7bcc1dda92e Mon Sep 17 00:00:00 2001 From: Erica Vellanoweth Date: Wed, 19 Aug 2026 18:00:06 -0700 Subject: [PATCH 3/3] Fix PR build infrastructure Upgrade SSH.NET to the secure release and adapt SCP path handling to the new API. Refresh GitHub Actions versions so the PR workflows run on supported action releases. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: f93cb371-279e-4917-ab3b-beed90f7f78d --- .github/workflows/pull-request-linux.yml | 8 ++++---- .github/workflows/pull-request.yml | 8 ++++---- Directory.Packages.props | 2 +- .../VirtualClient.Core.UnitTests/SshClientProxyTests.cs | 1 + src/VirtualClient/VirtualClient.Core/SshClientProxy.cs | 2 +- 5 files changed, 11 insertions(+), 10 deletions(-) diff --git a/.github/workflows/pull-request-linux.yml b/.github/workflows/pull-request-linux.yml index 77bd4109e9..99eed1c791 100644 --- a/.github/workflows/pull-request-linux.yml +++ b/.github/workflows/pull-request-linux.yml @@ -26,10 +26,10 @@ jobs: # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup .NET - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: dotnet-version: 9.0.x @@ -37,7 +37,7 @@ jobs: run: chmod -R +x *.sh - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -54,6 +54,6 @@ jobs: run: ./build-test.sh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index e14ccf3cd6..0c7be0e4e8 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -26,16 +26,16 @@ jobs: # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup .NET - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: dotnet-version: 9.0.x - name: Initialize CodeQL if: false # this is causing PR to fail on Windows with single slashes on Windows, temporarily disabling since we have it in Linux PR - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -53,6 +53,6 @@ jobs: - name: Perform CodeQL Analysis if: false # this is causing PR to fail on Windows with single slashes on Windows, temporarily disabling since we have it in Linux PR - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 with: category: "/language:${{matrix.language}}" diff --git a/Directory.Packages.props b/Directory.Packages.props index 462f3d2d23..19086b92d6 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -29,7 +29,7 @@ - + diff --git a/src/VirtualClient/VirtualClient.Core.UnitTests/SshClientProxyTests.cs b/src/VirtualClient/VirtualClient.Core.UnitTests/SshClientProxyTests.cs index 159a55f5a6..4a45157334 100644 --- a/src/VirtualClient/VirtualClient.Core.UnitTests/SshClientProxyTests.cs +++ b/src/VirtualClient/VirtualClient.Core.UnitTests/SshClientProxyTests.cs @@ -77,6 +77,7 @@ public void SshClientProxyConstructorsSetPropertiesToExpectedValues() Assert.IsTrue(object.ReferenceEquals(expectedInfo, client.ConnectionInfo)); Assert.IsTrue(object.ReferenceEquals(expectedInfo, client.SessionClient.ConnectionInfo)); Assert.IsTrue(object.ReferenceEquals(expectedInfo, client.SessionScpClient.ConnectionInfo)); + Assert.IsTrue(object.ReferenceEquals(RemotePathTransformation.ShellQuote, client.SessionScpClient.RemotePathTransformation)); } } diff --git a/src/VirtualClient/VirtualClient.Core/SshClientProxy.cs b/src/VirtualClient/VirtualClient.Core/SshClientProxy.cs index 17a6d637cf..7ffeabc92d 100644 --- a/src/VirtualClient/VirtualClient.Core/SshClientProxy.cs +++ b/src/VirtualClient/VirtualClient.Core/SshClientProxy.cs @@ -34,7 +34,7 @@ public SshClientProxy(ConnectionInfo connectionInfo) { connectionInfo.ThrowIfNull(nameof(connectionInfo)); this.SessionClient = new SshClient(connectionInfo); - this.SessionScpClient = new ScpClient(connectionInfo); + this.SessionScpClient = new ScpClient(connectionInfo, RemotePathTransformation.ShellQuote); } ///