diff --git a/src/VirtualClient/TestResources/Results-OpenSSL-server.txt b/src/VirtualClient/TestResources/Results-OpenSSL-server.txt new file mode 100644 index 0000000000..5a13d79aeb --- /dev/null +++ b/src/VirtualClient/TestResources/Results-OpenSSL-server.txt @@ -0,0 +1,2 @@ +Using default temp DH parameters +ACCEPT \ No newline at end of file diff --git a/src/VirtualClient/TestResources/Results-OpenSSL-stime.txt b/src/VirtualClient/TestResources/Results-OpenSSL-stime.txt new file mode 100644 index 0000000000..bd8e1c1191 --- /dev/null +++ b/src/VirtualClient/TestResources/Results-OpenSSL-stime.txt @@ -0,0 +1,13 @@ +Collecting connection statistics for 5 seconds +***************** + +17 connections in 1.54s; 11.04 connections/user sec, bytes read 9126806252 +17 connections in 6 real seconds, 536870956 bytes read per connection + + +Now timing with session id reuse. +starting +rrrrrrrrrrrrrrrrr + +17 connections in 1.54s; 11.04 connections/user sec, bytes read 9126806252 +17 connections in 6 real seconds, 536870956 bytes read per connection \ No newline at end of file diff --git a/src/VirtualClient/VirtualClient.Actions.FunctionalTests/OpenSSL/TlsOpenSslClientProfileTests.cs b/src/VirtualClient/VirtualClient.Actions.FunctionalTests/OpenSSL/TlsOpenSslClientProfileTests.cs new file mode 100644 index 0000000000..537e05d51a --- /dev/null +++ b/src/VirtualClient/VirtualClient.Actions.FunctionalTests/OpenSSL/TlsOpenSslClientProfileTests.cs @@ -0,0 +1,74 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace VirtualClient.Actions +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Net; + using System.Runtime.InteropServices; + using System.Text; + using System.Threading; + using System.Threading.Tasks; + using Moq; + using NUnit.Framework; + using VirtualClient.Common; + using VirtualClient.Contracts; + + [TestFixture] + [Category("Functional")] + public class TlsOpenSslClientProfileTests + { + private DependencyFixture mockFixture; + + [SetUp] + public void SetupFixture() + { + this.mockFixture = new DependencyFixture(); + this.mockFixture + .Setup(PlatformID.Unix, Architecture.X64, "Client01") + .SetupLayout( + new ClientInstance("Client01", "1.2.3.4", "Client"), + new ClientInstance("Server01", "1.2.3.5", "Server")); + + ComponentTypeCache.Instance.LoadComponentTypes(TestDependencies.TestDirectory); + } + + [Test] + [TestCase("Compete-OPENSSL-TLS.json")] + public async Task TlsOpenSslClientCreatesExpectedStateAndExecutesWorkload(string profile) + { + List expectedCommands = new List(); + + // Setup the expectations for the workload + // - Workload package is installed and exists. + // - Workload binaries/executables exist on the file system. + // - Expected processes are executed. + IPAddress.TryParse("1.2.3.4", out IPAddress ipAddress); + IApiClient apiClient = this.mockFixture.ApiClientManager.GetOrCreateApiClient("1.2.3.4", ipAddress); + + State state = new State(); + + await apiClient.CreateStateAsync(nameof(State), state, CancellationToken.None); + + this.mockFixture.ProcessManager.OnCreateProcess = (command, arguments, workingDir) => + { + IProcessProxy process = this.mockFixture.CreateProcess(command, arguments, workingDir); + if (arguments?.Contains("s_time") == true) + { + return process; + } + + return process; + }; + + using (ProfileExecutor executor = TestDependencies.CreateProfileExecutor(profile, this.mockFixture.Dependencies, dependenciesOnly: true)) + { + await executor.ExecuteAsync(ProfileTiming.OneIteration(), CancellationToken.None); + WorkloadAssert.WorkloadPackageInstalled(this.mockFixture, "openssl"); + WorkloadAssert.CommandsExecuted(this.mockFixture, expectedCommands.ToArray()); + } + } + } +} \ No newline at end of file diff --git a/src/VirtualClient/VirtualClient.Actions.FunctionalTests/OpenSSL/TlsOpenSslServerProfileTests.cs b/src/VirtualClient/VirtualClient.Actions.FunctionalTests/OpenSSL/TlsOpenSslServerProfileTests.cs new file mode 100644 index 0000000000..f051b91eea --- /dev/null +++ b/src/VirtualClient/VirtualClient.Actions.FunctionalTests/OpenSSL/TlsOpenSslServerProfileTests.cs @@ -0,0 +1,79 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace VirtualClient.Actions +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Net; + using System.Runtime.InteropServices; + using System.Text; + using System.Threading; + using System.Threading.Tasks; + using AutoFixture; + using Moq; + using NUnit.Framework; + using VirtualClient.Common; + using VirtualClient.Contracts; + + [TestFixture] + [Category("Functional")] + public class TlsOpenSslServerProfileTests + { + private DependencyFixture mockFixture; + + [SetUp] + public void SetupFixture() + { + this.mockFixture = new DependencyFixture(); + this.mockFixture + .Setup(PlatformID.Unix, Architecture.X64, "Server01") + .SetupLayout( + new ClientInstance("Client01", "1.2.3.4", "Client"), + new ClientInstance("Server01", "1.2.3.5", "Server")); + + ComponentTypeCache.Instance.LoadComponentTypes(TestDependencies.TestDirectory); + } + + [Test] + [TestCase("Compete-OPENSSL-TLS.json")] + public async Task TlsOpenSslServerWorkloadProfileInstallsTheExpectedDependenciesOnUnixPlatform(string profile) + { + // Setup the expectations for the workload + // - Workload package is installed and exists. + IPAddress.TryParse("1.2.3.5", out IPAddress ipAddress); + IApiClient apiClient = this.mockFixture.ApiClientManager.GetOrCreateApiClient("1.2.3.5", ipAddress); + + State state = new State(); + + await apiClient.CreateStateAsync(nameof(State), state, CancellationToken.None); + + using (ProfileExecutor executor = TestDependencies.CreateProfileExecutor(profile, this.mockFixture.Dependencies, dependenciesOnly: true)) + { + await executor.ExecuteAsync(ProfileTiming.OneIteration(), CancellationToken.None).ConfigureAwait(false); + + // Workload dependency package expectations + // The workload dependency package should have been installed at this point. + WorkloadAssert.WorkloadPackageInstalled(this.mockFixture, "openssl"); + } + } + + [Test] + [TestCase("Compete-OPENSSL-TLS.json")] + public void TlsOpenSslServerWorkloadProfileActionsWillNotBeExecutedIfTheWorkloadPackageDoesNotExist(string profile) + { + // We ensure the workload package does not exist. + this.mockFixture.PackageManager.Clear(); + + using (ProfileExecutor executor = TestDependencies.CreateProfileExecutor(profile, this.mockFixture.Dependencies)) + { + executor.ExecuteDependencies = false; + + DependencyException error = Assert.ThrowsAsync(() => executor.ExecuteAsync(ProfileTiming.OneIteration(), CancellationToken.None)); + Assert.AreEqual(ErrorReason.WorkloadDependencyMissing, error.Reason); + Assert.IsFalse(this.mockFixture.ProcessManager.Commands.Contains("openssl s_server")); + } + } + } +} diff --git a/src/VirtualClient/VirtualClient.Actions.UnitTests/OpenSSL/TlsOpenSslClientExecutorTests.cs b/src/VirtualClient/VirtualClient.Actions.UnitTests/OpenSSL/TlsOpenSslClientExecutorTests.cs new file mode 100644 index 0000000000..f7eb88be93 --- /dev/null +++ b/src/VirtualClient/VirtualClient.Actions.UnitTests/OpenSSL/TlsOpenSslClientExecutorTests.cs @@ -0,0 +1,162 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace VirtualClient.Actions +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Net; + using System.Runtime.InteropServices; + using System.Threading; + using System.Threading.Tasks; + using VirtualClient.Common; + using Moq; + using NUnit.Framework; + using VirtualClient.Actions.Properties; + using VirtualClient.Common.Telemetry; + using VirtualClient.Contracts; + + [TestFixture] + [Category("Unit")] + public class TlsOpenSslClientExecutorTests + { + private DependencyFixture fixture; + private DependencyPath mockPackage; + + [Test] + [TestCase(PlatformID.Unix, Architecture.X64, "linux-x64/bin/openssl")] + [TestCase(PlatformID.Unix, Architecture.Arm64, "linux-arm64/bin/openssl")] + public async Task Constructor_InitializesDependenciesAndPolicies(PlatformID platform, Architecture architecture, string binaryPath) + { + SetupEnvironment(platform, architecture); + using (TestOpenSslClientExecutor executor = new TestOpenSslClientExecutor(this.fixture, this.fixture.Parameters)) + { + + Assert.IsNotNull(executor); + Assert.IsNull(executor.ExecutablePath); + + await executor.InitializeAsync(EventContext.None, CancellationToken.None) + .ConfigureAwait(false); + + DependencyPath expectedWorkloadPackage = this.fixture.PlatformSpecifics.ToPlatformSpecificPath(this.mockPackage, platform, architecture); + Assert.IsTrue(expectedWorkloadPackage.Equals(executor.Package)); + + + string expectedWorkloadExecutablePath = Path.Combine(this.mockPackage.Path, binaryPath); + Assert.IsTrue(PlatformAgnosticPathComparer.Instance.Equals(expectedWorkloadExecutablePath, executor.ExecutablePath)); + } + } + + [Test] + [TestCase(PlatformID.Unix, Architecture.X64)] + [TestCase(PlatformID.Unix, Architecture.Arm64)] + public void Properties_ReturnExpectedValues(PlatformID platform, Architecture architecture) + { + SetupEnvironment(platform, architecture); + var executor = new TlsOpenSslClientExecutor(this.fixture.Dependencies, this.fixture.Parameters); + + Assert.IsNotNull(executor); + Assert.AreEqual(2, executor.ClientInstances); + Assert.AreEqual(443, executor.ServerPort); + Assert.IsTrue(executor.WarmUp); + Assert.IsNotNull(executor.Parameters); + } + + + [Test] + public void CaptureMetrics_LogsMetricsOnSuccess() + { + SetupEnvironment(PlatformID.Unix, Architecture.X64); + var executor = new TlsOpenSslClientExecutor(this.fixture.Dependencies, this.fixture.Parameters); + Assert.IsNotNull(executor); + var process = this.fixture.CreateProcess("openssl", "s_time ...", "/tmp"); + process.ExitCode = 0; + process.StandardOutput.Append(TestResources.Results_OpenSSL_stime); + + var method = typeof(TlsOpenSslClientExecutor).GetMethod("CaptureMetrics", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); + + // Should not throw + Assert.DoesNotThrow(() => + { + method.Invoke(executor, new object[] { process, "s_time ...", EventContext.None, CancellationToken.None }); + }); + } + + private void SetupEnvironment(PlatformID platform = PlatformID.Unix, Architecture architecture = Architecture.X64) + { + // Setup the default behaviors given all expected dependencies are in place such that the + // workload can be executed successfully. + this.fixture = new DependencyFixture(); + this.fixture.Setup(platform, architecture); + this.fixture + .Setup(platform, architecture, "Client01") + .SetupLayout( + new ClientInstance("Client01", "1.2.3.4", "Client"), + new ClientInstance("Server01", "1.2.3.5", "Server")); + + // ComponentTypeCache.Instance.LoadComponentTypes(TestDependencies.TestDirectory); + + this.fixture.SetupPackage( + "OpenSSL", + expectedFiles: $"{PlatformSpecifics.GetPlatformArchitectureName(platform, architecture)}/bin/openssl"); + + this.mockPackage = this.fixture.PackageManager.First(pkg => pkg.Name == "OpenSSL"); + + this.fixture.Parameters = new Dictionary + { + { "ClientInstances", 2 }, + { "ServerPort", 443 }, + { "WarmUp", true }, + { "CommandArguments", "s_time -connect :443 -www /test_1k.html -time 30 -ciphersuites TLS_AES_256_GCM_SHA384 -tls1_3" }, + { "Scenario", "OpenSSL_TLS_Client_AES_256_GCM_SHA384_1k" }, + { "MetricScenario", "tls_client_aes-256-gcm-sha384-1k" }, + { "PackageName", "OpenSSL" }, + { "Tags", "CPU,OpenSSL,Cryptography" }, + { "Role", "Client" } + }; + this.fixture.ProcessManager.OnProcessCreated = (process) => + { + // When we start the OpenSSL process we want to register a successful + // result. + if (process.IsMatch("openssl s_time")) + { + process.StandardOutput.Append(TestResources.Results_OpenSSL_stime); + } + }; + } + private class TestOpenSslClientExecutor : TlsOpenSslClientExecutor + { + public TestOpenSslClientExecutor(DependencyFixture mockFixture, IDictionary parameters) + : base(mockFixture.Dependencies, parameters) + { + } + + public new DependencyPath Package + { + get + { + return base.Package; + } + } + + public new Task ExecuteAsync(EventContext telemetryContext, CancellationToken cancellationToken) + { + return base.ExecuteAsync(telemetryContext, cancellationToken); + } + + public new Task InitializeAsync(EventContext telemetryContext, CancellationToken cancellationToken) + { + return base.InitializeAsync(telemetryContext, cancellationToken); + } + public IApiClient MockApiClient + { + get + { + return base.ApiClient; + } + } + } + } +} \ No newline at end of file diff --git a/src/VirtualClient/VirtualClient.Actions.UnitTests/Properties/TestResources.Designer.cs b/src/VirtualClient/VirtualClient.Actions.UnitTests/Properties/TestResources.Designer.cs index b23e1d9616..745e850aa5 100644 --- a/src/VirtualClient/VirtualClient.Actions.UnitTests/Properties/TestResources.Designer.cs +++ b/src/VirtualClient/VirtualClient.Actions.UnitTests/Properties/TestResources.Designer.cs @@ -1,4 +1,4 @@ -//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ // // This code was generated by a tool. // Runtime Version:4.0.30319.42000 @@ -199,5 +199,26 @@ internal static string Results_OpenSSL_speed { return ResourceManager.GetString("Results_OpenSSL_speed", resourceCulture); } } + + /// + /// Looks up a localized string similar to Collecting connection statistics for 5 seconds + ///***************** + /// + ///17 connections in 1.54s; 11.04 connections/user sec, bytes read 9126806252 + ///17 connections in 6 real seconds, 536870956 bytes read per connection + /// + /// + ///Now timing with session id reuse. + ///starting + ///rrrrrrrrrrrrrrrrr + /// + ///17 connections in 1.54s; 11.04 connections/user sec, bytes read 9126806252 + ///17 connections in 6 real seconds, 536870956 bytes read per connection. + /// + internal static string Results_OpenSSL_stime { + get { + return ResourceManager.GetString("Results_OpenSSL_stime", resourceCulture); + } + } } } diff --git a/src/VirtualClient/VirtualClient.Actions.UnitTests/Properties/TestResources.resx b/src/VirtualClient/VirtualClient.Actions.UnitTests/Properties/TestResources.resx index 323b861e2a..e3cf2ace97 100644 --- a/src/VirtualClient/VirtualClient.Actions.UnitTests/Properties/TestResources.resx +++ b/src/VirtualClient/VirtualClient.Actions.UnitTests/Properties/TestResources.resx @@ -118,6 +118,9 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + ..\Resources\Results-OpenSSL-stime.txt;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8 + ..\Resources\Results_DiskSpd_Text.txt;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;Windows-1252 diff --git a/src/VirtualClient/VirtualClient.Actions.UnitTests/Resources/Results-OpenSSL-stime.txt b/src/VirtualClient/VirtualClient.Actions.UnitTests/Resources/Results-OpenSSL-stime.txt new file mode 100644 index 0000000000..bd8e1c1191 --- /dev/null +++ b/src/VirtualClient/VirtualClient.Actions.UnitTests/Resources/Results-OpenSSL-stime.txt @@ -0,0 +1,13 @@ +Collecting connection statistics for 5 seconds +***************** + +17 connections in 1.54s; 11.04 connections/user sec, bytes read 9126806252 +17 connections in 6 real seconds, 536870956 bytes read per connection + + +Now timing with session id reuse. +starting +rrrrrrrrrrrrrrrrr + +17 connections in 1.54s; 11.04 connections/user sec, bytes read 9126806252 +17 connections in 6 real seconds, 536870956 bytes read per connection \ No newline at end of file diff --git a/src/VirtualClient/VirtualClient.Actions/OpenSSL/OpenSslExecutor.cs b/src/VirtualClient/VirtualClient.Actions/OpenSSL/OpenSslExecutor.cs index fefe7350e8..918e3ca53a 100644 --- a/src/VirtualClient/VirtualClient.Actions/OpenSSL/OpenSslExecutor.cs +++ b/src/VirtualClient/VirtualClient.Actions/OpenSSL/OpenSslExecutor.cs @@ -91,6 +91,88 @@ await this.InitializeWorkloadToolsetsAsync(cancellationToken) .ConfigureAwait(false); } + /// + /// Sets the environment variables required for running the OpenSSL workload. + /// + protected void SetEnvironmentVariables(IProcessProxy process) + { + if (this.Platform == PlatformID.Win32NT) + { + // The OpenSSL toolset we use was compiled using the Visual Studio 2019 C++ compiler. This creates a runtime + // dependency on the VC runtime .dlls. These are packaged with the OpenSSL package itself in the 'vcruntime' + // folder. We append the location of these VC runtime .dlls to the PATH environment variable so they can be + // found. + string vcRuntimeDllPath = this.PlatformSpecifics.Combine(this.Package.Path, "vcruntime"); + string currentPathValue = process.EnvironmentVariables["Path"]?.TrimEnd(';'); + + this.Logger.LogTraceMessage($"Setting Environment Variable:Path={currentPathValue};{vcRuntimeDllPath}", EventContext.Persisted()); + process.EnvironmentVariables["Path"] = $"{currentPathValue};{vcRuntimeDllPath}"; + } + else if (this.Platform == PlatformID.Unix) + { + // OpenSSL is typically installed on Linux systems by default. However, we want to use the package version that + // we compiled to ensure we are always running the exact same toolset across Windows and Linux systems. To ensure + // the package version of OpenSSL we use can find its lib/library files required to run, we have to set a special + // environment variable $LD_LIBRARY_PATH + string libPath = this.PlatformSpecifics.Combine(this.Package.Path, "lib64"); + + this.Logger.LogTraceMessage($"Setting Environment Variable:LD_LIBRARY_PATH={libPath}", EventContext.Persisted()); + process.EnvironmentVariables["LD_LIBRARY_PATH"] = libPath; + } + } + + /// + /// Initializes the location of the OpenSSL package on the system. + /// + protected async Task InitializePackageLocationAsync(CancellationToken cancellationToken) + { + if (!cancellationToken.IsCancellationRequested) + { + DependencyPath workloadPackage = await this.systemManagement.PackageManager.GetPackageAsync(this.PackageName, CancellationToken.None) + .ConfigureAwait(false); + + if (workloadPackage == null) + { + throw new DependencyException( + $"The expected package '{this.PackageName}' does not exist on the system or is not registered.", + ErrorReason.WorkloadDependencyMissing); + } + + workloadPackage = this.PlatformSpecifics.ToPlatformSpecificPath(workloadPackage, this.Platform, this.CpuArchitecture); + + this.Package = workloadPackage; + } + } + + /// + /// Initializes the OpenSSL workload toolsets (e.g. executable, libraries, etc.) on the system. + /// + protected async Task InitializeWorkloadToolsetsAsync(CancellationToken cancellationToken) + { + if (!cancellationToken.IsCancellationRequested) + { + if (this.Platform == PlatformID.Unix) + { + this.ExecutablePath = this.PlatformSpecifics.Combine(this.Package.Path, "bin", "openssl"); + + this.fileSystem.File.ThrowIfFileDoesNotExist( + this.ExecutablePath, + $"OpenSSL executable not found at path '{this.ExecutablePath}'"); + + await this.systemManagement.MakeFileExecutableAsync(this.ExecutablePath, this.Platform, cancellationToken) + .ConfigureAwait(false); + } + else if (this.Platform == PlatformID.Win32NT) + { + this.ExecutablePath = this.PlatformSpecifics.Combine(this.Package.Path, "bin", "openssl.exe"); + + this.fileSystem.File.ThrowIfFileDoesNotExist( + this.ExecutablePath, + $"OpenSSL executable not found at path '{this.ExecutablePath}'"); + } + } + } + /// /// Gets openssl version by running openssl version command. /// @@ -226,77 +308,5 @@ private string GetCommandArguments() return commandArguments; } - private async Task InitializePackageLocationAsync(CancellationToken cancellationToken) - { - if (!cancellationToken.IsCancellationRequested) - { - DependencyPath workloadPackage = await this.systemManagement.PackageManager.GetPackageAsync(this.PackageName, CancellationToken.None) - .ConfigureAwait(false); - - if (workloadPackage == null) - { - throw new DependencyException( - $"The expected package '{this.PackageName}' does not exist on the system or is not registered.", - ErrorReason.WorkloadDependencyMissing); - } - - workloadPackage = this.PlatformSpecifics.ToPlatformSpecificPath(workloadPackage, this.Platform, this.CpuArchitecture); - - this.Package = workloadPackage; - } - } - - private async Task InitializeWorkloadToolsetsAsync(CancellationToken cancellationToken) - { - if (!cancellationToken.IsCancellationRequested) - { - if (this.Platform == PlatformID.Unix) - { - this.ExecutablePath = this.PlatformSpecifics.Combine(this.Package.Path, "bin", "openssl"); - - this.fileSystem.File.ThrowIfFileDoesNotExist( - this.ExecutablePath, - $"OpenSSL executable not found at path '{this.ExecutablePath}'"); - - await this.systemManagement.MakeFileExecutableAsync(this.ExecutablePath, this.Platform, cancellationToken) - .ConfigureAwait(false); - } - else if (this.Platform == PlatformID.Win32NT) - { - this.ExecutablePath = this.PlatformSpecifics.Combine(this.Package.Path, "bin", "openssl.exe"); - - this.fileSystem.File.ThrowIfFileDoesNotExist( - this.ExecutablePath, - $"OpenSSL executable not found at path '{this.ExecutablePath}'"); - } - } - } - - private void SetEnvironmentVariables(IProcessProxy process) - { - if (this.Platform == PlatformID.Win32NT) - { - // The OpenSSL toolset we use was compiled using the Visual Studio 2019 C++ compiler. This creates a runtime - // dependency on the VC runtime .dlls. These are packaged with the OpenSSL package itself in the 'vcruntime' - // folder. We append the location of these VC runtime .dlls to the PATH environment variable so they can be - // found. - string vcRuntimeDllPath = this.PlatformSpecifics.Combine(this.Package.Path, "vcruntime"); - string currentPathValue = process.EnvironmentVariables["Path"]?.TrimEnd(';'); - - this.Logger.LogTraceMessage($"Setting Environment Variable:Path={currentPathValue};{vcRuntimeDllPath}", EventContext.Persisted()); - process.EnvironmentVariables["Path"] = $"{currentPathValue};{vcRuntimeDllPath}"; - } - else if (this.Platform == PlatformID.Unix) - { - // OpenSSL is typically installed on Linux systems by default. However, we want to use the package version that - // we compiled to ensure we are always running the exact same toolset across Windows and Linux systems. To ensure - // the package version of OpenSSL we use can find its lib/library files required to run, we have to set a special - // environment variable $LD_LIBRARY_PATH - string libPath = this.PlatformSpecifics.Combine(this.Package.Path, "lib64"); - - this.Logger.LogTraceMessage($"Setting Environment Variable:LD_LIBRARY_PATH={libPath}", EventContext.Persisted()); - process.EnvironmentVariables["LD_LIBRARY_PATH"] = libPath; - } - } } } \ No newline at end of file diff --git a/src/VirtualClient/VirtualClient.Actions/OpenSSL/TlsOpenSslClientExecutor.cs b/src/VirtualClient/VirtualClient.Actions/OpenSSL/TlsOpenSslClientExecutor.cs new file mode 100644 index 0000000000..a846dc2c4c --- /dev/null +++ b/src/VirtualClient/VirtualClient.Actions/OpenSSL/TlsOpenSslClientExecutor.cs @@ -0,0 +1,344 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace VirtualClient.Actions +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Net; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.Logging; + using Polly; + using VirtualClient; + using VirtualClient.Common; + using VirtualClient.Common.Contracts; + using VirtualClient.Common.Extensions; + using VirtualClient.Common.Telemetry; + using VirtualClient.Contracts; + + /// + /// Executes the OpenSSL TLS client workload. Inherits from OpenSslExecutor. + /// + public class TlsOpenSslClientExecutor : OpenSslExecutor + { + private readonly object lockObject = new object(); + private ISystemManagement systemManagement; + + /// + /// Constructor for the OpenSSL client executor. + /// + /// Provides required dependencies to the component. + /// Parameters defined in the profile or supplied on the command line. + public TlsOpenSslClientExecutor(IServiceCollection dependencies, IDictionary parameters = null) + : base(dependencies, parameters) + { + this.ClientFlowRetryPolicy = Policy.Handle(exc => !(exc is OperationCanceledException)) + .WaitAndRetryAsync(3, (retries) => TimeSpan.FromSeconds(retries * 2)); + + this.ClientRetryPolicy = Policy.Handle(exc => !(exc is OperationCanceledException)) + .WaitAndRetryAsync(3, (retries) => TimeSpan.FromSeconds(retries)); + + this.PollingTimeout = TimeSpan.FromMinutes(40); + + this.ApiClientManager = dependencies.GetService(); + this.systemManagement = dependencies.GetService(); + } + + /// + /// Parameter defines the number of client instances to execute against + /// each server instance. Default = # of logical cores/vCPUs on system. + /// + public int ClientInstances + { + get + { + return this.Parameters.GetValue(nameof(this.ClientInstances), 1); + } + } + + /// + /// gets the OpenSSL server port used for communication. + /// + public int ServerPort + { + get + { + return this.Parameters.GetValue(nameof(this.ServerPort)); + } + } + + /// + /// Parameter defines true/false whether the action is meant to warm up the server. + /// We do not capture metrics on warm up operations. + /// + public bool WarmUp + { + get + { + return this.Parameters.GetValue(nameof(this.WarmUp), false); + } + } + + /// + /// Client used to communicate with the hosted instance of the + /// Virtual Client API at server side. + /// + protected IApiClient ServerApiClient { get; set; } + + /// + /// Provides the ability to create API clients for interacting with local as well as remote instances + /// of the Virtual Client API service. + /// + protected IApiClientManager ApiClientManager { get; } + + /// + /// The retry policy to apply to the client-side execution workflow. + /// + protected IAsyncPolicy ClientFlowRetryPolicy { get; set; } + + /// + /// The retry policy to apply to each client workload instance when trying to startup + /// against a target server. + /// + protected IAsyncPolicy ClientRetryPolicy { get; set; } + + /// + /// True/false whether the Redis server instance has been warmed up. + /// + protected bool IsServerWarmedUp { get; set; } + + /// + /// The timespan at which the client will poll the server for responses before + /// timing out. + /// + protected TimeSpan PollingTimeout { get; set; } + + /// + /// Client used to communicate with the locally hosted instance of the + /// Virtual Client API. + /// + protected IApiClient ApiClient + { + get + { + return this.ServerApiClient; + } + } + + /// + /// Executes client side. + /// + protected override async Task ExecuteAsync(EventContext telemetryContext, CancellationToken cancellationToken) + { + if (!this.WarmUp || !this.IsServerWarmedUp) + { + IPAddress ipAddress; + List clientWorkloadTasks = new List(); + + if (this.IsMultiRoleLayout()) + { + IEnumerable targetServers = this.GetLayoutClientInstances(ClientRole.Server); + foreach (ClientInstance server in targetServers) + { + // Reliability/Recovery: + // The pattern here is to allow for any steps within the workflow to fail and to simply start the entire workflow + // over again. + clientWorkloadTasks.Add(this.ClientFlowRetryPolicy.ExecuteAsync(async () => + { + if (!cancellationToken.IsCancellationRequested) + { + IApiClient serverApiClient = this.ApiClientManager.GetOrCreateApiClient(server.Name, server); + + // 1) Confirm server is online. + // =========================================================================== + this.Logger.LogTraceMessage("Synchronization: Poll server API for heartbeat..."); + await serverApiClient.PollForHeartbeatAsync(this.PollingTimeout, cancellationToken); + + // 2) Confirm the server-side application is online. + // =========================================================================== + this.Logger.LogTraceMessage("Synchronization: Poll server for online signal..."); + await serverApiClient.PollForServerOnlineAsync(TimeSpan.FromSeconds(30), cancellationToken); + + this.Logger.LogTraceMessage("Synchronization: Server online signal confirmed..."); + this.Logger.LogTraceMessage("Synchronization: Start client workload..."); + + // 3) Get Parameters required. + State serverState = await this.GetServerStateAsync(serverApiClient, cancellationToken); + + // 4) Execute the client workload. + // =========================================================================== + ipAddress = IPAddress.Parse(server.IPAddress); + + await this.ExecuteWorkloadsAsync(ipAddress, telemetryContext, cancellationToken); + } + })); + } + } + else + { + ipAddress = IPAddress.Loopback; + clientWorkloadTasks.Add(this.ClientFlowRetryPolicy.ExecuteAsync(async () => + { + if (!cancellationToken.IsCancellationRequested) + { + State serverState = await this.GetServerStateAsync(this.ServerApiClient, cancellationToken); + await this.ExecuteWorkloadsAsync(ipAddress, telemetryContext, cancellationToken); + } + })); + } + + await Task.WhenAll(clientWorkloadTasks); + + if (this.WarmUp) + { + this.IsServerWarmedUp = true; + } + } + } + + /// + /// Initializes the environment and dependencies for client of redis Benchmark workload. + /// + /// Provides context information that will be captured with telemetry events. + /// A token that can be used to cancel the operation. + /// + protected override async Task InitializeAsync(EventContext telemetryContext, CancellationToken cancellationToken) + { + await base.InitializeAsync(telemetryContext, cancellationToken); + + if (this.IsMultiRoleLayout()) + { + ClientInstance clientInstance = this.GetLayoutClientInstance(); + string layoutIPAddress = clientInstance.IPAddress; + + this.ThrowIfLayoutClientIPAddressNotFound(layoutIPAddress); + this.ThrowIfRoleNotSupported(clientInstance.Role); + } + + this.InitializeApiClients(); + } + + /// + /// Initializes API client. + /// + private void InitializeApiClients() + { + IApiClientManager clientManager = this.Dependencies.GetService(); + bool isSingleVM = !this.IsMultiRoleLayout(); + + if (isSingleVM) + { + this.ServerApiClient = clientManager.GetOrCreateApiClient(IPAddress.Loopback.ToString(), IPAddress.Loopback); + } + else + { + ClientInstance serverInstance = this.GetLayoutClientInstances(ClientRole.Server).First(); + IPAddress.TryParse(serverInstance.IPAddress, out IPAddress serverIPAddress); + + this.ServerApiClient = clientManager.GetOrCreateApiClient(serverIPAddress.ToString(), serverIPAddress); + this.RegisterToSendExitNotifications($"{this.TypeName}.ExitNotification", this.ServerApiClient); + } + } + + private Task ExecuteWorkloadsAsync(IPAddress serverIPAddress, EventContext telemetryContext, CancellationToken cancellationToken) + { + string commandArguments = this.Parameters.GetValue(nameof(this.CommandArguments)); + EventContext relatedContext = telemetryContext.Clone() + .AddContext("executable", this.ExecutablePath) + .AddContext("commandArguments", commandArguments); + // ex: command + // s_time -connect :{ServerPort} -www /test_1k.html -time {Duration.TotalSeconds} -ciphersuites TLS_AES_256_GCM_SHA384 + // insert IP address before port at index 16 + string fullCommand = commandArguments.Insert(16, serverIPAddress.ToString()); + + return this.Logger.LogMessageAsync($"{nameof(TlsOpenSslClientExecutor)}.ExecuteOpenSSL_Client_Workload", relatedContext.Clone(), async () => + { + using (BackgroundOperations profiling = BackgroundOperations.BeginProfiling(this, cancellationToken)) + { + using (IProcessProxy process = this.systemManagement.ProcessManager.CreateProcess(this.ExecutablePath, fullCommand)) + { + this.SetEnvironmentVariables(process); + this.CleanupTasks.Add(() => process.SafeKill()); + + try + { + await process.StartAndWaitAsync(cancellationToken).ConfigureAwait(); + + if (!cancellationToken.IsCancellationRequested) + { + await this.LogProcessDetailsAsync(process, telemetryContext, "OpenSSLClient", logToFile: true); + + process.ThrowIfWorkloadFailed(); + this.CaptureMetrics(process, fullCommand, telemetryContext, cancellationToken); + } + } + finally + { + if (!process.HasExited) + { + process.Kill(); + } + } + } + + } + }); + } + + private async Task GetServerStateAsync(IApiClient serverApiClient, CancellationToken cancellationToken) + { + Item state = await serverApiClient.GetStateAsync( + nameof(State), + cancellationToken); + + if (state == null) + { + throw new WorkloadException( + $"Expected server state information missing. The openssl tls server did not return state indicating the details for the server(s) running.", + ErrorReason.WorkloadUnexpectedAnomaly); + } + + return state.Definition; + } + + private void CaptureMetrics(IProcessProxy workloadProcess, string commandArguments, EventContext telemetryContext, CancellationToken cancellationToken) + { + if (workloadProcess.ExitCode == 0) + { + try + { + // Retrieve OpenSSL version + // await this.GetOpenSslVersionAsync(workloadProcess.FullCommand(), cancellationToken); + + this.MetadataContract.Apply(telemetryContext); + + OpenSslTlsMetricsParser resultsParser = new OpenSslTlsMetricsParser(workloadProcess.StandardOutput.ToString(), commandArguments); + IList metrics = resultsParser.Parse(); + + this.Logger.LogMetrics( + "OpenSSL_tls_client", + this.MetricScenario ?? this.Scenario, + workloadProcess.StartTime, + workloadProcess.ExitTime, + metrics, + null, + commandArguments, + this.Tags, + telemetryContext); + + metrics.LogConsole(this.Scenario, "OpenSSL_tls_client"); + } + catch (SchemaException exc) + { + EventContext relatedContext = telemetryContext.Clone() + .AddError(exc); + + this.Logger.LogMessage($"{nameof(TlsOpenSslClientExecutor)}.WorkloadOutputParsingFailed", LogLevel.Warning, relatedContext); + } + } + } + } +} \ No newline at end of file diff --git a/src/VirtualClient/VirtualClient.Actions/OpenSSL/TlsOpenSslMetricsParser.cs b/src/VirtualClient/VirtualClient.Actions/OpenSSL/TlsOpenSslMetricsParser.cs new file mode 100644 index 0000000000..779a7ccd05 --- /dev/null +++ b/src/VirtualClient/VirtualClient.Actions/OpenSSL/TlsOpenSslMetricsParser.cs @@ -0,0 +1,142 @@ +namespace VirtualClient.Actions +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Text.RegularExpressions; + using System.Threading.Tasks; + using MathNet.Numerics.Integration; + using Microsoft.CodeAnalysis.CSharp.Syntax; + using VirtualClient.Common.Extensions; + using VirtualClient.Contracts; + + /// + /// openssl s_time metrics parser + /// + public class OpenSslTlsMetricsParser : MetricsParser + { + private const string TotalBytesRead = "TotalBytesRead"; + private const string NoOfConnections = "NumberOfConnections"; + private const string Duration = "Duration"; + private const string BytesperConnection = "BytesReadPerConnection"; + private const string NewConnThroughput = "NewConnectionThroughput"; + private const string NewConnPersec = "NewConnectionsPerSec"; + + private const string ReuseTotalBytesRead = "ReuseTotalBytesRead"; + private const string ReuseNoOfConnections = "ReuseNumberOfConnections"; + private const string ReuseDuration = "ReuseDuration"; + private const string ReuseBytesperConnection = "ReuseBytesReadPerConnection"; + private const string ReuseConnThroughput = "ReuseConnectionThroughput"; + private const string ReuseConnPerSec = "ReuseConnectionsPerSec"; + + /// + /// parse output of openssl s_time + /// + public OpenSslTlsMetricsParser(string resultsText, string commandArguments) + : base(resultsText) + { + commandArguments.ThrowIfNullOrWhiteSpace(nameof(commandArguments)); + this.CommandArguments = commandArguments; + } + + /// + /// The command line arguments provided to the OpenSSL command (e.g. speed -elapsed -seconds 100 -multi 4 aes-256-cbc). + /// + public string CommandArguments { get; } + + /// + /// parser function + /// + /// + public override IList Parse() + { + List metrics = new List(); + var parsedMetrics = this.ParseSTimeOutput(); + double newConnThroughput = parsedMetrics[TotalBytesRead] / parsedMetrics[Duration]; + double reuseConnThroughput = parsedMetrics[ReuseTotalBytesRead] / parsedMetrics[ReuseDuration]; + double newConnPerSec = parsedMetrics[NoOfConnections] / parsedMetrics[Duration]; + double reuseConnPerSec = parsedMetrics[ReuseNoOfConnections] / parsedMetrics[ReuseDuration]; + + // add metrics to the list - + + // set 1 - new connection metrics + metrics.Add(new Metric(TotalBytesRead, parsedMetrics[TotalBytesRead], MetricUnit.Bytes, MetricRelativity.HigherIsBetter, verbosity: 0)); + metrics.Add(new Metric(NoOfConnections, parsedMetrics[NoOfConnections], MetricUnit.Count, MetricRelativity.HigherIsBetter, verbosity: 0)); + metrics.Add(new Metric(Duration, parsedMetrics[Duration], MetricUnit.Seconds)); + metrics.Add(new Metric(BytesperConnection, parsedMetrics[BytesperConnection], MetricUnit.BytesPerConnection, MetricRelativity.HigherIsBetter, verbosity: 0)); + metrics.Add(new Metric(NewConnThroughput, newConnThroughput, MetricUnit.BytesPerSecond, MetricRelativity.HigherIsBetter, verbosity: 0)); + metrics.Add(new Metric(NewConnPersec, newConnPerSec, MetricUnit.Count, MetricRelativity.HigherIsBetter, verbosity: 0)); + + // set 2 - reuse connection metrics + metrics.Add(new Metric(ReuseTotalBytesRead, parsedMetrics[ReuseTotalBytesRead], MetricUnit.Bytes, MetricRelativity.HigherIsBetter, verbosity: 0)); + metrics.Add(new Metric(ReuseNoOfConnections, parsedMetrics[ReuseNoOfConnections], MetricUnit.Count, MetricRelativity.HigherIsBetter, verbosity: 0)); + metrics.Add(new Metric(ReuseDuration, parsedMetrics[ReuseDuration], MetricUnit.Seconds)); + metrics.Add(new Metric(ReuseBytesperConnection, parsedMetrics[ReuseBytesperConnection], MetricUnit.BytesPerConnection, MetricRelativity.HigherIsBetter, verbosity: 0)); + metrics.Add(new Metric(ReuseConnThroughput, reuseConnThroughput, MetricUnit.BytesPerSecond, MetricRelativity.HigherIsBetter, verbosity: 0)); + metrics.Add(new Metric(ReuseConnPerSec, reuseConnPerSec, MetricUnit.Count, MetricRelativity.HigherIsBetter, verbosity: 0)); + + return metrics; + } + + /// + /// parse s_time output and extract metrics + /// + /// metric names and values + public Dictionary ParseSTimeOutput() + { + string input = this.RawText; + var metrics = new Dictionary(); + + /* + * output from s_time + * Collecting connection statistics for 5 seconds + ... + 1184 connections in 0.78s; 1517.95 connections/user sec, bytes read 382432 + 1184 connections in 6 real seconds, 323 bytes read per connection + ... + Now timing with session id reuse. + starting + ... + 1696 connections in 0.86s; 1972.09 connections/user sec, bytes read 547808 + 1696 connections in 6 real seconds, 323 bytes read per connection + */ + + // ignore the first connections and connections per user metric as it is conflicting with + // total connections in n seconds + + // Initial run + var match1 = Regex.Match(input, @"(\d+) connections in ([\d.]+)s; ([\d.]+) connections/user sec, bytes read (\d+)"); + if (match1.Success) + { + metrics[TotalBytesRead] = Convert.ToDouble(match1.Groups[4].Value); + } + + var match2 = Regex.Match(input, @"(\d+) connections in (\d+) real seconds, (\d+) bytes read per connection"); + if (match2.Success) + { + metrics[NoOfConnections] = Convert.ToDouble(match2.Groups[1].Value); + metrics[Duration] = Convert.ToDouble(match2.Groups[2].Value); + metrics[BytesperConnection] = Convert.ToDouble(match2.Groups[3].Value); + } + + // Session reuse run + var reuseSection = input.Substring(input.IndexOf("Now timing with session id reuse.")); + var match3 = Regex.Match(reuseSection, @"(\d+) connections in ([\d.]+)s; ([\d.]+) connections/user sec, bytes read (\d+)"); + if (match3.Success) + { + metrics[ReuseTotalBytesRead] = Convert.ToDouble(match3.Groups[4].Value); + } + + var match4 = Regex.Match(reuseSection, @"(\d+) connections in (\d+) real seconds, (\d+) bytes read per connection"); + if (match4.Success) + { + metrics[ReuseNoOfConnections] = Convert.ToDouble(match4.Groups[1].Value); + metrics[ReuseDuration] = Convert.ToDouble(match4.Groups[2].Value); + metrics[ReuseBytesperConnection] = Convert.ToDouble(match4.Groups[3].Value); + } + + return metrics; + } + } +} diff --git a/src/VirtualClient/VirtualClient.Actions/OpenSSL/TlsOpenSslServerExecutor.cs b/src/VirtualClient/VirtualClient.Actions/OpenSSL/TlsOpenSslServerExecutor.cs new file mode 100644 index 0000000000..36432bea78 --- /dev/null +++ b/src/VirtualClient/VirtualClient.Actions/OpenSSL/TlsOpenSslServerExecutor.cs @@ -0,0 +1,344 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace VirtualClient.Actions +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.IO; + using System.IO.Abstractions; + using System.Linq; + using System.Net; + using System.Net.Http; + using System.Text.RegularExpressions; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.Logging; + using VirtualClient.Common; + using VirtualClient.Common.Contracts; + using VirtualClient.Common.Extensions; + using VirtualClient.Common.Telemetry; + using VirtualClient.Contracts; + using VirtualClient.Logging; + + /// + /// Executes the OpenSSL TLS server workload. Inherits from OpenSslExecutor. + /// + public class TlsOpenSslServerExecutor : OpenSslExecutor + { + private List serverProcesses; + private bool disposed; + private ISystemManagement systemManagement; + private IFileSystem fileSystem; + + /// + /// Constructor for the OpenSSL server executor. + /// + /// Provides required dependencies to the component. + /// Parameters defined in the profile or supplied on the command line. + public TlsOpenSslServerExecutor(IServiceCollection dependencies, IDictionary parameters) + : base(dependencies, parameters) + { + this.ApiClientManager = dependencies.GetService(); + this.systemManagement = dependencies.GetService(); + this.serverProcesses = new List(); + this.disposed = false; + this.fileSystem = dependencies.GetService(); + } + + /// + /// Server Port used for communication. + /// + public int ServerPort + { + get + { + return this.Parameters.GetValue(nameof(this.ServerPort)); + } + } + + /// + /// Client used to communicate with the hosted instance of the + /// Virtual Client API at server side. + /// + protected IApiClient ServerApiClient { get; set; } + + /// + /// Provides the ability to create API clients for interacting with local as well as remote instances + /// of the Virtual Client API service. + /// + protected IApiClientManager ApiClientManager { get; } + + /// + /// Cancellation Token Source for Server. + /// + protected CancellationTokenSource ServerCancellationSource { get; set; } + + /// + /// Server IpAddress on which openssl s_server runs. + /// + protected string ServerIpAddress { get; set; } + + /// + /// Client used to communicate with the locally hosted instance of the + /// Virtual Client API. + /// + protected IApiClient ApiClient + { + get + { + return this.ServerApiClient; + } + } + + /// + /// Disposes of resources used by the executor including shutting down any + /// instances of openssl s_server running. + /// + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (!this.disposed && this.serverProcesses.Any()) + { + try + { + // We MUST stop the server instances from running before VC exits or they will + // continue running until explicitly stopped. This is a problem for running server + // workloads back to back because the requisite ports will be in use already on next + // VC startup. + this.KillServerInstancesAsync(CancellationToken.None) + .GetAwaiter().GetResult(); + } + catch + { + // Best effort + } + + this.disposed = true; + } + } + } + + /// + /// Initializes the API clients used to communicate with the server instance. + /// + protected override async Task InitializeAsync(EventContext telemetryContext, CancellationToken cancellationToken) + { + await base.InitializeAsync(telemetryContext, cancellationToken); + + this.InitializeApiClients(telemetryContext, cancellationToken); + + if (this.IsMultiRoleLayout()) + { + ClientInstance clientInstance = this.GetLayoutClientInstance(); + string layoutIPAddress = clientInstance.IPAddress; + + this.ThrowIfLayoutClientIPAddressNotFound(layoutIPAddress); + this.ThrowIfRoleNotSupported(clientInstance.Role); + } + + // copy resource files to openssl package directory + string sourceDir = Path.Combine(this.Package.Path, "../../../tls-resources"); + string destDir = Path.Combine(this.Package.Path, "bin"); + + if (this.fileSystem.Directory.Exists(sourceDir)) + { + var htmlFiles = this.fileSystem.Directory.GetFiles(sourceDir, "*.html"); + foreach (var file in htmlFiles) + { + string destFile = this.fileSystem.Path.Combine(destDir, this.fileSystem.Path.GetFileName(file)); + this.fileSystem.File.Copy(file, destFile, overwrite: true); + } + } + else + { + throw new FileNotFoundException($"The source directory '{sourceDir}' does not exist. Cannot copy TLS resource files."); + } + + } + + /// + /// Initializes the API clients used to communicate with the server instance. + /// + protected override Task ExecuteAsync(EventContext telemetryContext, CancellationToken cancellationToken) + { + return this.Logger.LogMessageAsync($"{nameof(TlsOpenSslServerExecutor)}.ExecuteServer", telemetryContext, async () => + { + using (this.ServerCancellationSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken)) + { + try + { + await this.ServerApiClient.PollForHeartbeatAsync(TimeSpan.FromMinutes(5), cancellationToken); + if (this.ResetServer(telemetryContext)) + { + await this.DeleteStateAsync(telemetryContext, cancellationToken); + await this.KillServerInstancesAsync(cancellationToken); + } + + await this.SaveStateAsync(telemetryContext, cancellationToken); + this.SetServerOnline(true); + + if (this.IsMultiRoleLayout()) + { + using (BackgroundOperations profiling = BackgroundOperations.BeginProfiling(this, cancellationToken)) + { + await this.StartServerInstancesAsync(telemetryContext, cancellationToken); + } + } + } + finally + { + this.SetServerOnline(false); + await this.KillServerInstancesAsync(cancellationToken); + } + } + }); + } + + private Task DeleteStateAsync(EventContext telemetryContext, CancellationToken cancellationToken) + { + EventContext relatedContext = telemetryContext.Clone(); + return this.Logger.LogMessageAsync($"{this.TypeName}.DeleteState", relatedContext, async () => + { + using (HttpResponseMessage response = await this.ApiClient.DeleteStateAsync(nameof(State), cancellationToken)) + { + relatedContext.AddResponseContext(response); + if (response.StatusCode != HttpStatusCode.NoContent) + { + response.ThrowOnError(ErrorReason.HttpNonSuccessResponse); + } + } + }); + } + + private Task KillServerInstancesAsync(CancellationToken cancellationToken) + { + this.Logger.LogTraceMessage($"{this.TypeName}.KillServerInstances"); + IEnumerable processes = this.systemManagement.ProcessManager.GetProcesses("openssl"); + + if (processes?.Any() == true) + { + foreach (IProcessProxy process in processes) + { + process.SafeKill(); + } + } + + return this.WaitAsync(TimeSpan.FromSeconds(3), cancellationToken); + } + + private bool ResetServer(EventContext telemetryContext) + { + bool shouldReset = true; + if (this.serverProcesses?.Any() == true) + { + // Depending upon how the server Task instances are created, the Task may be in a status + // of Running or WaitingForActivation. The server is running in either of these 2 states. + shouldReset = !this.serverProcesses.All(p => p.Status == TaskStatus.Running || p.Status == TaskStatus.WaitingForActivation); + } + + if (shouldReset) + { + this.Logger.LogTraceMessage($"Restart openssl s_server Server(s)...", telemetryContext); + } + else + { + this.Logger.LogTraceMessage($"openssl s_server Running...", telemetryContext); + } + + return shouldReset; + } + + /// + /// Initializes API client. + /// + private void InitializeApiClients(EventContext telemetryContext, CancellationToken cancellationToken) + { + if (!cancellationToken.IsCancellationRequested) + { + IApiClientManager clientManager = this.Dependencies.GetService(); + bool isSingleVM = !this.IsMultiRoleLayout(); + + if (isSingleVM) + { + this.ServerApiClient = clientManager.GetOrCreateApiClient(IPAddress.Loopback.ToString(), IPAddress.Loopback); + } + else + { + ClientInstance serverInstance = this.GetLayoutClientInstances(ClientRole.Server).First(); + IPAddress.TryParse(serverInstance.IPAddress, out IPAddress serverIPAddress); + + this.ServerApiClient = clientManager.GetOrCreateApiClient(serverIPAddress.ToString(), serverIPAddress); + this.RegisterToSendExitNotifications($"{this.TypeName}.ExitNotification", this.ServerApiClient); + } + } + } + + private Task SaveStateAsync(EventContext telemetryContext, CancellationToken cancellationToken) + { + EventContext relatedContext = telemetryContext.Clone(); + return this.Logger.LogMessageAsync($"{this.TypeName}.SaveState", relatedContext, async () => + { + var state = new Item(nameof(State), new State()); + + using (HttpResponseMessage response = await this.ApiClient.UpdateStateAsync(nameof(State), state, cancellationToken)) + { + relatedContext.AddResponseContext(response); + response.ThrowOnError(ErrorReason.HttpNonSuccessResponse); + } + }); + } + + private Task StartServerInstancesAsync(EventContext telemetryContext, CancellationToken cancellationToken) + { + this.serverProcesses.Clear(); + + string commandArguments = this.Parameters.GetValue(nameof(this.CommandArguments)); + // prefix path to cert and key files + // "s_server -accept {ServerPort} -cert server.crt -key server.key -tls1_3 -WWW" + + string certPath = this.Package.Path + "/../../../tls-resources/"; + + commandArguments = Regex.Replace(commandArguments, @"-cert\s+(\S+)", $"-cert {certPath}$1"); + commandArguments = Regex.Replace(commandArguments, @"-key\s+(\S+)", $"-key {certPath}$1"); + + EventContext relatedContext = telemetryContext.Clone() + .AddContext("executable", this.ExecutablePath) + .AddContext("commandArguments", commandArguments); + + return this.Logger.LogMessageAsync($"{nameof(TlsOpenSslServerExecutor)}.ExecuteWorkload", relatedContext, async () => + { + using (BackgroundOperations profiling = BackgroundOperations.BeginProfiling(this, cancellationToken)) + { + using (IProcessProxy process = this.systemManagement.ProcessManager.CreateProcess(this.ExecutablePath, commandArguments)) + { + this.SetEnvironmentVariables(process); + this.CleanupTasks.Add(() => process.SafeKill()); + + try + { + await process.StartAndWaitAsync(cancellationToken).ConfigureAwait(); + + if (!cancellationToken.IsCancellationRequested) + { + await this.LogProcessDetailsAsync(process, telemetryContext, "OpenSSL", logToFile: true); + + process.ThrowIfWorkloadFailed(successCodes: new int[] { 0, 137 }); + } + } + finally + { + if (!process.HasExited) + { + process.Kill(); + } + } + } + } + }); + } + } +} \ No newline at end of file diff --git a/src/VirtualClient/VirtualClient.Contracts/MetricUnit.cs b/src/VirtualClient/VirtualClient.Contracts/MetricUnit.cs index 123471ec82..cb933b1442 100644 --- a/src/VirtualClient/VirtualClient.Contracts/MetricUnit.cs +++ b/src/VirtualClient/VirtualClient.Contracts/MetricUnit.cs @@ -148,5 +148,10 @@ public class MetricUnit /// Watts /// public const string Watts = "watts"; + + /// + /// Bytes per connection + /// + public const string BytesPerConnection = "bytes/connection"; } } diff --git a/src/VirtualClient/VirtualClient.Main/profiles/Compete-OPENSSL-TLS.json b/src/VirtualClient/VirtualClient.Main/profiles/Compete-OPENSSL-TLS.json new file mode 100644 index 0000000000..182069c19a --- /dev/null +++ b/src/VirtualClient/VirtualClient.Main/profiles/Compete-OPENSSL-TLS.json @@ -0,0 +1,368 @@ +{ + "Description": "OpenSSL 3.5.0 Benchmark with TLS", + "Metadata": { + "RecommendedMinimumExecutionTime": "01:00:00", + "SupportedPlatforms": "linux-x64,linux-arm64", + "SupportedOperatingSystems": "Ubuntu" + }, + "Parameters": { + "ServerPort": "5000", + "ClientInstances": 1, + "Duration": "00:00:30" + }, + "Actions": [ + { + "Type": "TlsOpenSslServerExecutor", + "Parameters": { + "Scenario": "OpenSSL_TLS_Server", + "MetricScenario": "tls_server", + "CommandArguments": "s_server -accept {ServerPort} -cert server.crt -key server.key -tls1_3 -WWW", + "PackageName": "openssl", + "ServerPort": "$.Parameters.ServerPort", + "Tags": "CPU,OpenSSL,Cryptography", + "Role": "Server" + } + }, + { + "Type": "TlsOpenSslClientExecutor", + "Parameters": { + "Scenario": "OpenSSL_TLS_Client_AES_128_GCM_SHA256_1k", + "MetricScenario": "tls_client_aes-128-gcm-sha256-1k", + "CommandArguments": "s_time -connect :{ServerPort} -www /test_1k.html -time {Duration.TotalSeconds} -ciphersuites TLS_AES_128_GCM_SHA256 -tls1_3", + "Duration": "$.Parameters.Duration", + "ServerPort": "$.Parameters.ServerPort", + "PackageName": "openssl", + "Tags": "CPU,OpenSSL,Cryptography", + "Role": "Client" + } + }, + { + "Type": "TlsOpenSslClientExecutor", + "Parameters": { + "Scenario": "OpenSSL_TLS_Client_AES_128_GCM_SHA256_4k", + "MetricScenario": "tls_client_aes-128-gcm-sha256-4k", + "CommandArguments": "s_time -connect :{ServerPort} -www /test_4k.html -time {Duration.TotalSeconds} -ciphersuites TLS_AES_128_GCM_SHA256 -tls1_3", + "Duration": "$.Parameters.Duration", + "ServerPort": "$.Parameters.ServerPort", + "PackageName": "openssl", + "Tags": "CPU,OpenSSL,Cryptography", + "Role": "Client" + } + }, + { + "Type": "TlsOpenSslClientExecutor", + "Parameters": { + "Scenario": "OpenSSL_TLS_Client_AES_128_GCM_SHA256_16k_{Duration}", + "MetricScenario": "tls_client_aes-128-gcm-sha256-16k_{Duration}", + "CommandArguments": "s_time -connect :{ServerPort} -www /test_16k.html -time {Duration.TotalSeconds} -ciphersuites TLS_AES_128_GCM_SHA256 -tls1_3", + "Duration": "$.Parameters.Duration", + "ServerPort": "$.Parameters.ServerPort", + "PackageName": "openssl", + "Tags": "CPU,OpenSSL,Cryptography", + "Role": "Client" + } + }, + { + "Type": "TlsOpenSslClientExecutor", + "Parameters": { + "Scenario": "OpenSSL_TLS_Client_AES_128_GCM_SHA256_64k_{Duration}", + "MetricScenario": "tls_client_aes-128-gcm-sha256-64k_{Duration}", + "CommandArguments": "s_time -connect :{ServerPort} -www /test_64k.html -time {Duration.TotalSeconds} -ciphersuites TLS_AES_128_GCM_SHA256 -tls1_3", + "Duration": "$.Parameters.Duration", + "ServerPort": "$.Parameters.ServerPort", + "PackageName": "openssl", + "Tags": "CPU,OpenSSL,Cryptography", + "Role": "Client" + } + }, + { + "Type": "TlsOpenSslClientExecutor", + "Parameters": { + "Scenario": "OpenSSL_TLS_Client_AES_128_GCM_SHA256_256k_{Duration}", + "MetricScenario": "tls_client_aes-128-gcm-sha256-256k_{Duration}", + "CommandArguments": "s_time -connect :{ServerPort} -www /test_256k.html -time {Duration.TotalSeconds} -ciphersuites TLS_AES_128_GCM_SHA256 -tls1_3", + "Duration": "$.Parameters.Duration", + "ServerPort": "$.Parameters.ServerPort", + "PackageName": "openssl", + "Tags": "CPU,OpenSSL,Cryptography", + "Role": "Client" + } + }, + { + "Type": "TlsOpenSslClientExecutor", + "Parameters": { + "Scenario": "OpenSSL_TLS_Client_AES_128_GCM_SHA256_1mb_{Duration}", + "MetricScenario": "tls_client_aes-128-gcm-sha256-1mb_{Duration}", + "CommandArguments": "s_time -connect :{ServerPort} -www /test_1mb.html -time {Duration.TotalSeconds} -ciphersuites TLS_AES_128_GCM_SHA256 -tls1_3", + "Duration": "$.Parameters.Duration", + "ServerPort": "$.Parameters.ServerPort", + "PackageName": "openssl", + "Tags": "CPU,OpenSSL,Cryptography", + "Role": "Client" + } + }, + { + "Type": "TlsOpenSslClientExecutor", + "Parameters": { + "Scenario": "OpenSSL_TLS_Client_AES_128_GCM_SHA256_100mb_{Duration}", + "MetricScenario": "tls_client_aes-128-gcm-sha256-100mb_{Duration}", + "CommandArguments": "s_time -connect :{ServerPort} -www /test_100mb.html -time {Duration.TotalSeconds} -ciphersuites TLS_AES_128_GCM_SHA256 -tls1_3", + "Duration": "$.Parameters.Duration", + "ServerPort": "$.Parameters.ServerPort", + "PackageName": "openssl", + "Tags": "CPU,OpenSSL,Cryptography", + "Role": "Client" + } + }, + { + "Type": "TlsOpenSslClientExecutor", + "Parameters": { + "Scenario": "OpenSSL_TLS_Client_AES_128_GCM_SHA256_512mb", + "MetricScenario": "tls_client_aes-128-gcm-sha256-512mb", + "CommandArguments": "s_time -connect :{ServerPort} -www /test_512mb.html -time {Duration.TotalSeconds} -ciphersuites TLS_AES_128_GCM_SHA256 -tls1_3", + "Duration": "$.Parameters.Duration", + "ServerPort": "$.Parameters.ServerPort", + "PackageName": "openssl", + "Tags": "CPU,OpenSSL,Cryptography", + "Role": "Client" + } + }, + { + "Type": "TlsOpenSslClientExecutor", + "Parameters": { + "Scenario": "OpenSSL_TLS_Client_AES_256_GCM_SHA384_1k", + "MetricScenario": "tls_client_aes-256-gcm-sha384-1k", + "CommandArguments": "s_time -connect :{ServerPort} -www /test_1k.html -time {Duration.TotalSeconds} -ciphersuites TLS_AES_256_GCM_SHA384 -tls1_3", + "Duration": "$.Parameters.Duration", + "ServerPort": "$.Parameters.ServerPort", + "PackageName": "openssl", + "Tags": "CPU,OpenSSL,Cryptography", + "Role": "Client" + } + }, + { + "Type": "TlsOpenSslClientExecutor", + "Parameters": { + "Scenario": "OpenSSL_TLS_Client_AES_256_GCM_SHA384_4k", + "MetricScenario": "tls_client_aes-256-gcm-sha384-4k", + "CommandArguments": "s_time -connect :{ServerPort} -www /test_4k.html -time {Duration.TotalSeconds} -ciphersuites TLS_AES_256_GCM_SHA384 -tls1_3", + "Duration": "$.Parameters.Duration", + "ServerPort": "$.Parameters.ServerPort", + "PackageName": "openssl", + "Tags": "CPU,OpenSSL,Cryptography", + "Role": "Client" + } + }, + { + "Type": "TlsOpenSslClientExecutor", + "Parameters": { + "Scenario": "OpenSSL_TLS_Client_AES_256_GCM_SHA384_16k_{Duration}", + "MetricScenario": "tls_client_aes-256-gcm-sha384-16k_{Duration}", + "CommandArguments": "s_time -connect :{ServerPort} -www /test_16k.html -time {Duration.TotalSeconds} -ciphersuites TLS_AES_256_GCM_SHA384 -tls1_3", + "Duration": "$.Parameters.Duration", + "ServerPort": "$.Parameters.ServerPort", + "PackageName": "openssl", + "Tags": "CPU,OpenSSL,Cryptography", + "Role": "Client" + } + }, + { + "Type": "TlsOpenSslClientExecutor", + "Parameters": { + "Scenario": "OpenSSL_TLS_Client_AES_256_GCM_SHA384_64k_{Duration}", + "MetricScenario": "tls_client_aes-256-gcm-sha384-64k_{Duration}", + "CommandArguments": "s_time -connect :{ServerPort} -www /test_64k.html -time {Duration.TotalSeconds} -ciphersuites TLS_AES_256_GCM_SHA384 -tls1_3", + "Duration": "$.Parameters.Duration", + "ServerPort": "$.Parameters.ServerPort", + "PackageName": "openssl", + "Tags": "CPU,OpenSSL,Cryptography", + "Role": "Client" + } + }, + { + "Type": "TlsOpenSslClientExecutor", + "Parameters": { + "Scenario": "OpenSSL_TLS_Client_AES_256_GCM_SHA384_256k_{Duration}", + "MetricScenario": "tls_client_aes-256-gcm-sha384-256k_{Duration}", + "CommandArguments": "s_time -connect :{ServerPort} -www /test_256k.html -time {Duration.TotalSeconds} -ciphersuites TLS_AES_256_GCM_SHA384 -tls1_3", + "Duration": "$.Parameters.Duration", + "ServerPort": "$.Parameters.ServerPort", + "PackageName": "openssl", + "Tags": "CPU,OpenSSL,Cryptography", + "Role": "Client" + } + }, + { + "Type": "TlsOpenSslClientExecutor", + "Parameters": { + "Scenario": "OpenSSL_TLS_Client_AES_256_GCM_SHA384_1mb_{Duration}", + "MetricScenario": "tls_client_aes-256-gcm-sha384-1mb_{Duration}", + "CommandArguments": "s_time -connect :{ServerPort} -www /test_1mb.html -time {Duration.TotalSeconds} -ciphersuites TLS_AES_256_GCM_SHA384 -tls1_3", + "Duration": "$.Parameters.Duration", + "ServerPort": "$.Parameters.ServerPort", + "PackageName": "openssl", + "Tags": "CPU,OpenSSL,Cryptography", + "Role": "Client" + } + }, + { + "Type": "TlsOpenSslClientExecutor", + "Parameters": { + "Scenario": "OpenSSL_TLS_Client_AES_256_GCM_SHA384_100mb_{Duration}", + "MetricScenario": "tls_client_aes-256-gcm-sha384-100mb_{Duration}", + "CommandArguments": "s_time -connect :{ServerPort} -www /test_100mb.html -time {Duration.TotalSeconds} -ciphersuites TLS_AES_256_GCM_SHA384 -tls1_3", + "Duration": "$.Parameters.Duration", + "ServerPort": "$.Parameters.ServerPort", + "PackageName": "openssl", + "Tags": "CPU,OpenSSL,Cryptography", + "Role": "Client" + } + }, + { + "Type": "TlsOpenSslClientExecutor", + "Parameters": { + "Scenario": "OpenSSL_TLS_Client_AES_256_GCM_SHA384_512mb", + "MetricScenario": "tls_client_aes-256-gcm-sha384-512mb", + "CommandArguments": "s_time -connect :{ServerPort} -www /test_512mb.html -time {Duration.TotalSeconds} -ciphersuites TLS_AES_256_GCM_SHA384 -tls1_3", + "Duration": "$.Parameters.Duration", + "ServerPort": "$.Parameters.ServerPort", + "PackageName": "openssl", + "Tags": "CPU,OpenSSL,Cryptography", + "Role": "Client" + } + }, + { + "Type": "TlsOpenSslClientExecutor", + "Parameters": { + "Scenario": "OpenSSL_TLS_Client_CHACHA20_POLY1305_SHA256_1k", + "MetricScenario": "tls_client_chacha20-poly1305-sha256-1k", + "CommandArguments": "s_time -connect :{ServerPort} -www /test_1k.html -time {Duration.TotalSeconds} -ciphersuites TLS_CHACHA20_POLY1305_SHA256 -tls1_3", + "Duration": "$.Parameters.Duration", + "ServerPort": "$.Parameters.ServerPort", + "PackageName": "openssl", + "Tags": "CPU,OpenSSL,Cryptography", + "Role": "Client" + } + }, + { + "Type": "TlsOpenSslClientExecutor", + "Parameters": { + "Scenario": "OpenSSL_TLS_Client_CHACHA20_POLY1305_SHA256_4k", + "MetricScenario": "tls_client_chacha20-poly1305-sha256-4k", + "CommandArguments": "s_time -connect :{ServerPort} -www /test_4k.html -time {Duration.TotalSeconds} -ciphersuites TLS_CHACHA20_POLY1305_SHA256 -tls1_3", + "Duration": "$.Parameters.Duration", + "ServerPort": "$.Parameters.ServerPort", + "PackageName": "openssl", + "Tags": "CPU,OpenSSL,Cryptography", + "Role": "Client" + } + }, + { + "Type": "TlsOpenSslClientExecutor", + "Parameters": { + "Scenario": "OpenSSL_TLS_Client_CHACHA20_POLY1305_SHA256_16k_{Duration}", + "MetricScenario": "tls_client_chacha20-poly1305-sha256-16k_{Duration}", + "CommandArguments": "s_time -connect :{ServerPort} -www /test_16k.html -time {Duration.TotalSeconds} -ciphersuites TLS_CHACHA20_POLY1305_SHA256 -tls1_3", + "Duration": "$.Parameters.Duration", + "ServerPort": "$.Parameters.ServerPort", + "PackageName": "openssl", + "Tags": "CPU,OpenSSL,Cryptography", + "Role": "Client" + } + }, + { + "Type": "TlsOpenSslClientExecutor", + "Parameters": { + "Scenario": "OpenSSL_TLS_Client_CHACHA20_POLY1305_SHA256_64k_{Duration}", + "MetricScenario": "tls_client_chacha20-poly1305-sha256-64k_{Duration}", + "CommandArguments": "s_time -connect :{ServerPort} -www /test_64k.html -time {Duration.TotalSeconds} -ciphersuites TLS_CHACHA20_POLY1305_SHA256 -tls1_3", + "Duration": "$.Parameters.Duration", + "ServerPort": "$.Parameters.ServerPort", + "PackageName": "openssl", + "Tags": "CPU,OpenSSL,Cryptography", + "Role": "Client" + } + }, + { + "Type": "TlsOpenSslClientExecutor", + "Parameters": { + "Scenario": "OpenSSL_TLS_Client_CHACHA20_POLY1305_SHA256_256k_{Duration}", + "MetricScenario": "tls_client_chacha20-poly1305-sha256-256k_{Duration}", + "CommandArguments": "s_time -connect :{ServerPort} -www /test_256k.html -time {Duration.TotalSeconds} -ciphersuites TLS_CHACHA20_POLY1305_SHA256 -tls1_3", + "Duration": "$.Parameters.Duration", + "ServerPort": "$.Parameters.ServerPort", + "PackageName": "openssl", + "Tags": "CPU,OpenSSL,Cryptography", + "Role": "Client" + } + }, + { + "Type": "TlsOpenSslClientExecutor", + "Parameters": { + "Scenario": "OpenSSL_TLS_Client_CHACHA20_POLY1305_SHA256_1mb_{Duration}", + "MetricScenario": "tls_client_chacha20-poly1305-sha256-1mb_{Duration}", + "CommandArguments": "s_time -connect :{ServerPort} -www /test_1mb.html -time {Duration.TotalSeconds} -ciphersuites TLS_CHACHA20_POLY1305_SHA256 -tls1_3", + "Duration": "$.Parameters.Duration", + "ServerPort": "$.Parameters.ServerPort", + "PackageName": "openssl", + "Tags": "CPU,OpenSSL,Cryptography", + "Role": "Client" + } + }, + { + "Type": "TlsOpenSslClientExecutor", + "Parameters": { + "Scenario": "OpenSSL_TLS_Client_CHACHA20-POLY1305-SHA256-100mb_{Duration}", + "MetricScenario": "tls_client_chacha20_poly1305_sha256_100mb_{Duration}", + "CommandArguments": "s_time -connect :{ServerPort} -www /test_100mb.html -time {Duration.TotalSeconds} -ciphersuites TLS_CHACHA20_POLY1305_SHA256 -tls1_3", + "Duration": "$.Parameters.Duration", + "ServerPort": "$.Parameters.ServerPort", + "PackageName": "openssl", + "Tags": "CPU,OpenSSL,Cryptography", + "Role": "Client" + } + }, + { + "Type": "TlsOpenSslClientExecutor", + "Parameters": { + "Scenario": "OpenSSL_TLS_Client_CHACHA20_POLY1305_SHA256_512mb", + "MetricScenario": "tls_client_chacha20-poly1305-sha256-512mb", + "CommandArguments": "s_time -connect :{ServerPort} -www /test_512mb.html -time {Duration.TotalSeconds} -ciphersuites TLS_CHACHA20_POLY1305_SHA256 -tls1_3", + "Duration": "$.Parameters.Duration", + "ServerPort": "$.Parameters.ServerPort", + "PackageName": "openssl", + "Tags": "CPU,OpenSSL,Cryptography", + "Role": "Client" + } + } + ], + "Dependencies": [ + { + "Type": "DependencyPackageInstallation", + "Parameters": { + "Scenario": "InstallOpenSSLPackage", + "BlobContainer": "packages", + "BlobName": "openssl.3.5.0.zip", + "PackageName": "openssl", + "Extract": true + } + }, + { + "Type": "DependencyPackageInstallation", + "Parameters": { + "Scenario": "InstallTlsResourcesForTLSTesting", + "BlobContainer": "packages", + "BlobName": "tls-resources.zip", + "PackageName": "tls-resources", + "Extract": true, + "Role": "Server" + } + }, + { + "Type": "ApiServer", + "Parameters": { + "Scenario": "StartAPIServer" + } + } + ] +}