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.Actions.UnitTests/Sysbench/SysbenchConfigurationTests.cs b/src/VirtualClient/VirtualClient.Actions.UnitTests/Sysbench/SysbenchConfigurationTests.cs index f7ae18a06d..666829b907 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; @@ -117,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() { @@ -163,6 +208,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..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"; @@ -131,6 +145,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.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); } /// 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"