Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
2.1.35
2.1.36
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

namespace VirtualClient.Actions
{
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using NUnit.Framework;
using VirtualClient.Actions.NetworkPerformance;
using VirtualClient.Contracts;

[TestFixture]
[Category("Unit")]
public class NetworkPingExecutorTests
{
private MockFixture mockFixture;

[SetUp]
public void SetupTest()
{
this.mockFixture = new MockFixture();
}

[Test]
public void NetworkPingExecutorThrowsIfIPAddressIsNotDefined()
{
this.mockFixture.Parameters = new Dictionary<string, IConvertible>
{
{ nameof(NetworkPingExecutor.IPAddress), "NotDefined" }
};

using (NetworkPingExecutor executor = new NetworkPingExecutor(this.mockFixture.Dependencies, this.mockFixture.Parameters))
{
WorkloadException exception = Assert.ThrowsAsync<WorkloadException>(
() => executor.ExecuteAsync(CancellationToken.None));

Assert.AreEqual(ErrorReason.InstructionsNotProvided, exception.Reason);
Assert.IsTrue(exception.Message.Contains("IP address"));
}
}

[Test]
[TestCase("")]
[TestCase("invalid-ip")]
[TestCase("999.999.999.999")]
[TestCase("not.an.ip.address")]
public void NetworkPingExecutorThrowsIfIPAddressIsInvalid(string invalidIpAddress)
{
this.mockFixture.Parameters = new Dictionary<string, IConvertible>
{
{ nameof(NetworkPingExecutor.IPAddress), invalidIpAddress }
};

using (NetworkPingExecutor executor = new NetworkPingExecutor(this.mockFixture.Dependencies, this.mockFixture.Parameters))
{
WorkloadException exception = Assert.ThrowsAsync<WorkloadException>(
() => executor.ExecuteAsync(CancellationToken.None));

Assert.AreEqual(ErrorReason.InstructionsNotValid, exception.Reason);
Assert.IsTrue(exception.Message.Contains("Invalid IP address format"));
}
}

[Test]
public void NetworkPingExecutorParsesIPAddressParameter()
{
this.mockFixture.Parameters = new Dictionary<string, IConvertible>
{
{ nameof(NetworkPingExecutor.IPAddress), "192.168.1.1" }
};

using (NetworkPingExecutor executor = new NetworkPingExecutor(this.mockFixture.Dependencies, this.mockFixture.Parameters))
{
Assert.AreEqual("192.168.1.1", executor.IPAddress);
}
}

[Test]
[TestCase("192.168.1.1")]
[TestCase("10.0.0.1")]
[TestCase("2001:0db8:85a3:0000:0000:8a2e:0370:7334")]
[TestCase("::1")]
public void NetworkPingExecutorAcceptsValidIPAddresses(string ipAddress)
{
this.mockFixture.Parameters = new Dictionary<string, IConvertible>
{
{ nameof(NetworkPingExecutor.IPAddress), ipAddress }
};

using (NetworkPingExecutor executor = new NetworkPingExecutor(this.mockFixture.Dependencies, this.mockFixture.Parameters))
{
Assert.AreEqual(ipAddress, executor.IPAddress);
}
}

[Test]
public void NetworkPingExecutorUsesDefaultPingIterationsWhenNotSpecified()
{
this.mockFixture.Parameters = new Dictionary<string, IConvertible>
{
{ nameof(NetworkPingExecutor.IPAddress), "192.168.1.1" }
};

using (NetworkPingExecutor executor = new NetworkPingExecutor(this.mockFixture.Dependencies, this.mockFixture.Parameters))
{
Assert.AreEqual(50, executor.PingIterations);
}
}

[Test]
[TestCase(100)]
[TestCase(200)]
[TestCase(500)]
public void NetworkPingExecutorParsesPingIterationsParameter(int iterations)
{
this.mockFixture.Parameters = new Dictionary<string, IConvertible>
{
{ nameof(NetworkPingExecutor.IPAddress), "192.168.1.1" },
{ nameof(NetworkPingExecutor.PingIterations), iterations }
};

using (NetworkPingExecutor executor = new NetworkPingExecutor(this.mockFixture.Dependencies, this.mockFixture.Parameters))
{
Assert.AreEqual(iterations, executor.PingIterations);
}
}

[Test]
public void NetworkPingExecutorDurationIsNullWhenNotSpecified()
{
this.mockFixture.Parameters = new Dictionary<string, IConvertible>
{
{ nameof(NetworkPingExecutor.IPAddress), "192.168.1.1" }
};

using (NetworkPingExecutor executor = new NetworkPingExecutor(this.mockFixture.Dependencies, this.mockFixture.Parameters))
{
Assert.IsNull(executor.Duration);
}
}

[Test]
public void NetworkPingExecutorDurationIsNullWhenSetToEmptyString()
{
this.mockFixture.Parameters = new Dictionary<string, IConvertible>
{
{ nameof(NetworkPingExecutor.IPAddress), "192.168.1.1" },
{ nameof(NetworkPingExecutor.Duration), string.Empty }
};

using (NetworkPingExecutor executor = new NetworkPingExecutor(this.mockFixture.Dependencies, this.mockFixture.Parameters))
{
Assert.IsNull(executor.Duration);
}
}

[Test]
public void NetworkPingExecutorDurationIsNullWhenSetToNull()
{
this.mockFixture.Parameters = new Dictionary<string, IConvertible>
{
{ nameof(NetworkPingExecutor.IPAddress), "192.168.1.1" },
{ nameof(NetworkPingExecutor.Duration), null }
};

using (NetworkPingExecutor executor = new NetworkPingExecutor(this.mockFixture.Dependencies, this.mockFixture.Parameters))
{
Assert.IsNull(executor.Duration);
}
}

[Test]
[TestCase("00:05:00", 300)] // 5 minutes = 300 seconds
[TestCase("00:10:00", 600)] // 10 minutes = 600 seconds
[TestCase("01:00:00", 3600)] // 1 hour = 3600 seconds
[TestCase("00:00:30", 30)] // 30 seconds
public void NetworkPingExecutorParsesTimeSpanFormatForDuration(string durationString, int expectedSeconds)
{
this.mockFixture.Parameters = new Dictionary<string, IConvertible>
{
{ nameof(NetworkPingExecutor.IPAddress), "192.168.1.1" },
{ nameof(NetworkPingExecutor.Duration), durationString }
};

using (NetworkPingExecutor executor = new NetworkPingExecutor(this.mockFixture.Dependencies, this.mockFixture.Parameters))
{
Assert.IsNotNull(executor.Duration);
Assert.AreEqual(expectedSeconds, executor.Duration.Value.TotalSeconds);
}
}

[Test]
[TestCase(300)] // 300 seconds
[TestCase(600)] // 600 seconds
[TestCase(3600)] // 3600 seconds
public void NetworkPingExecutorParsesNumericFormatForDuration(int durationSeconds)
{
this.mockFixture.Parameters = new Dictionary<string, IConvertible>
{
{ nameof(NetworkPingExecutor.IPAddress), "192.168.1.1" },
{ nameof(NetworkPingExecutor.Duration), durationSeconds }
};

using (NetworkPingExecutor executor = new NetworkPingExecutor(this.mockFixture.Dependencies, this.mockFixture.Parameters))
{
Assert.IsNotNull(executor.Duration);
Assert.AreEqual(durationSeconds, executor.Duration.Value.TotalSeconds);
}
}

[Test]
public void NetworkPingExecutorSupportsSettingBothDurationAndPingIterations()
{
// When Duration is set, it takes precedence over PingIterations
this.mockFixture.Parameters = new Dictionary<string, IConvertible>
{
{ nameof(NetworkPingExecutor.IPAddress), "192.168.1.1" },
{ nameof(NetworkPingExecutor.Duration), "00:05:00" },
{ nameof(NetworkPingExecutor.PingIterations), 100 }
};

using (NetworkPingExecutor executor = new NetworkPingExecutor(this.mockFixture.Dependencies, this.mockFixture.Parameters))
{
Assert.IsNotNull(executor.Duration);
Assert.AreEqual(300, executor.Duration.Value.TotalSeconds);
Assert.AreEqual(100, executor.PingIterations);
}
}

[Test]
public void NetworkPingExecutorParametersAreConsistentWithProfile()
{
// Verify that the parameters match what's expected in the PERF-NETWORK-PING.json profile
this.mockFixture.Parameters = new Dictionary<string, IConvertible>
{
{ nameof(NetworkPingExecutor.IPAddress), "192.168.1.1" },
{ nameof(NetworkPingExecutor.Duration), null },
{ nameof(NetworkPingExecutor.PingIterations), 300 }
};

using (NetworkPingExecutor executor = new NetworkPingExecutor(this.mockFixture.Dependencies, this.mockFixture.Parameters))
{
Assert.AreEqual("192.168.1.1", executor.IPAddress);
Assert.IsNull(executor.Duration);
Assert.AreEqual(300, executor.PingIterations);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,28 @@ public int PingIterations
}
}

/// <summary>
/// Parameter. Defines the duration for which the network ping test will run.
/// This can be a valid timespan (e.g. 00:10:00) or a simple numeric value representing total seconds (e.g. 600).
/// </summary>
public TimeSpan? Duration
{
get
{
// Check if the parameter exists and is not empty
if (this.Parameters.ContainsKey(nameof(NetworkPingExecutor.Duration)))
{
string durationValue = this.Parameters[nameof(NetworkPingExecutor.Duration)]?.ToString();
if (!string.IsNullOrWhiteSpace(durationValue))
{
return this.Parameters.GetTimeSpanValue(nameof(NetworkPingExecutor.Duration));
}
}

return null;
}
}

/// <summary>
/// The retry policy to apply to ping operations for handling transient
/// issues/errors.
Expand Down Expand Up @@ -116,8 +138,18 @@ private async Task ExecutePingServerAsync(IPAddress ipAddress, EventContext tele

DateTime startTime = DateTime.UtcNow;
Stopwatch blipTimer = Stopwatch.StartNew();
while (iterations < this.PingIterations && !cancellationToken.IsCancellationRequested)

// Use Duration if specified, otherwise use PingIterations
DateTime stopTime = startTime.Add(this.Duration ?? TimeSpan.Zero);

while (!cancellationToken.IsCancellationRequested)
{
if ((this.Duration != null && DateTime.UtcNow >= stopTime) ||
(this.Duration == null && iterations >= this.PingIterations))
{
break;
}

try
{
await this.PingRetryPolicy.ExecuteAsync(async () =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,18 @@
"SupportedOperatingSystems": "CBL-Mariner,CentOS,Debian,RedHat,Suse,Ubuntu,Windows"
},
"Parameters": {
"IPAddress": "NotDefined"
"IPAddress": "NotDefined",
"Duration": null,
"PingIterations": 300
},
"Actions": [
{
"Type": "NetworkPingExecutor",
"Parameters": {
"Scenario": "ICMP",
"IPAddress": "$.Parameters.IPAddress",
"PingIterations": 300,
"Duration": "$.Parameters.Duration",
"PingIterations": "$.Parameters.PingIterations",
"Tags": "Performance,Networking,Ping"
}
}
Expand Down
16 changes: 12 additions & 4 deletions website/docs/workloads/network-ping/network-ping-profiles.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,11 @@ respond to network ping requests.
* **Profile Parameters**
The following parameters can be optionally supplied on the command line to modify the behaviors of the workload.

| Parameter | Purpose |
|-------------|---------|
| IPAddress | Required. The IP address of the target endpoint to which to send network pings and measure round trip response times. Loopback address can be used: 127.0.0.1. |
| Parameter | Purpose | Default Value |
|----------------|---------|---------------|
| IPAddress | Required. The IP address of the target endpoint to which to send network pings and measure round trip response times. Loopback address can be used: 127.0.0.1. | NotDefined |
| Duration | Optional. The duration for which the network ping test will run. This can be a valid timespan (e.g. 00:10:00 for 10 minutes) or a simple numeric value representing total seconds (e.g. 600). When specified, it overrides PingIterations. | null (uses PingIterations) |
| PingIterations | Optional. The number of individual network pings that will be conducted. This is used when Duration is not specified. | 300 |

* **Profile Runtimes**
See the 'Metadata' section of the profile for estimated runtimes. These timings represent the length of time required to run a single round of profile
Expand All @@ -43,6 +45,12 @@ respond to network ping requests.
The following section provides a few basic examples of how to use the workload profile.

``` bash
# Execute the workload profile
# Execute the workload profile with default behavior (300 ping iterations)
VirtualClient.exe --profile=PERF-NETWORK-PING.json --system=Demo --timeout=1440 --parameters=IPAddress=1.2.3.4

# Execute the workload profile with custom duration (10 minutes)
VirtualClient.exe --profile=PERF-NETWORK-PING.json --system=Demo --timeout=1440 --parameters=IPAddress=1.2.3.4,,,Duration=00:10:00

# Execute the workload profile with custom ping iterations
VirtualClient.exe --profile=PERF-NETWORK-PING.json --system=Demo --timeout=1440 --parameters=IPAddress=1.2.3.4,,,PingIterations=500
```
Loading