Skip to content

Commit 4540d31

Browse files
authored
Implement support for mapping exit codes to status codes for integration with orchestration systems. (#595)
Review not required.
1 parent 3d47f22 commit 4540d31

14 files changed

Lines changed: 362 additions & 63 deletions

File tree

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
2.1.42
1+
2.1.43

src/VirtualClient/VirtualClient.Actions.UnitTests/Network2/CPS/CPSExecutorTests2.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,7 @@ public void CPSExecutorThrowsIfAnUnsupportedRoleIsSupplied_Linux(PlatformID plat
254254
using (TestCPSExecutor executor = new TestCPSExecutor(this.mockFixture.Dependencies, this.mockFixture.Parameters))
255255
{
256256
var exception = Assert.ThrowsAsync<DependencyException>(() => executor.ExecuteAsync(CancellationToken.None));
257-
Assert.AreEqual(ErrorReason.EnvironmentLayoutClientInstancesNotFound, exception.Reason);
257+
Assert.AreEqual(ErrorReason.LayoutInvalid, exception.Reason);
258258
}
259259
}
260260

@@ -273,7 +273,7 @@ public void CPSExecutorThrowsIfAnUnsupportedRoleIsSupplied_Windows(PlatformID pl
273273
using (TestCPSExecutor executor = new TestCPSExecutor(this.mockFixture.Dependencies, this.mockFixture.Parameters))
274274
{
275275
var exception = Assert.ThrowsAsync<DependencyException>(() => executor.ExecuteAsync(CancellationToken.None));
276-
Assert.AreEqual(ErrorReason.EnvironmentLayoutClientInstancesNotFound, exception.Reason);
276+
Assert.AreEqual(ErrorReason.LayoutInvalid, exception.Reason);
277277
}
278278
}
279279

@@ -289,7 +289,7 @@ public void CPSExecutorThrowsWhenASpecificRoleIsNotDefined_Linux(PlatformID plat
289289
using (TestCPSExecutor executor = new TestCPSExecutor(this.mockFixture.Dependencies, this.mockFixture.Parameters))
290290
{
291291
var exception = Assert.ThrowsAsync<DependencyException>(() => executor.ExecuteAsync(CancellationToken.None));
292-
Assert.AreEqual(ErrorReason.EnvironmentLayoutNotDefined, exception.Reason);
292+
Assert.AreEqual(ErrorReason.LayoutNotDefined, exception.Reason);
293293
}
294294
}
295295

@@ -306,7 +306,7 @@ public void CPSExecutorThrowsWhenASpecificRoleIsNotDefined_Windows(PlatformID pl
306306
using (TestCPSExecutor executor = new TestCPSExecutor(this.mockFixture.Dependencies, this.mockFixture.Parameters))
307307
{
308308
var exception = Assert.ThrowsAsync<DependencyException>(() => executor.ExecuteAsync(CancellationToken.None));
309-
Assert.AreEqual(ErrorReason.EnvironmentLayoutNotDefined, exception.Reason);
309+
Assert.AreEqual(ErrorReason.LayoutNotDefined, exception.Reason);
310310
}
311311
}
312312

src/VirtualClient/VirtualClient.Actions.UnitTests/Network2/Latte/LatteExecutorTests2.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ public void LatteExecutorThrowsIfAnUnsupportedRoleIsSupplied(PlatformID platform
187187
using (TestLatteExecutor component = new TestLatteExecutor(this.mockFixture.Dependencies, this.mockFixture.Parameters))
188188
{
189189
var exception = Assert.ThrowsAsync<DependencyException>(() => component.ExecuteAsync(CancellationToken.None));
190-
Assert.AreEqual(ErrorReason.EnvironmentLayoutClientInstancesNotFound, exception.Reason);
190+
Assert.AreEqual(ErrorReason.LayoutInvalid, exception.Reason);
191191
}
192192
}
193193

@@ -204,7 +204,7 @@ public void LatteExecutorThrowsWhenASpecificRoleIsNotDefined(PlatformID platform
204204
using (TestLatteExecutor component = new TestLatteExecutor(this.mockFixture.Dependencies, this.mockFixture.Parameters))
205205
{
206206
var exception = Assert.ThrowsAsync<DependencyException>(() => component.ExecuteAsync(CancellationToken.None));
207-
Assert.AreEqual(ErrorReason.EnvironmentLayoutNotDefined, exception.Reason);
207+
Assert.AreEqual(ErrorReason.LayoutNotDefined, exception.Reason);
208208
}
209209
}
210210

src/VirtualClient/VirtualClient.Actions.UnitTests/Network2/NTttcp/NTttcpExecutorTests2.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,7 @@ public void NTttcpExecutorThrowsIfAnUnsupportedRoleIsSupplied_Unix(PlatformID pl
285285
using (TestNTttcpExecutor executor = new TestNTttcpExecutor(this.mockFixture.Dependencies, this.mockFixture.Parameters))
286286
{
287287
var exception = Assert.ThrowsAsync<DependencyException>(() => executor.ExecuteAsync(CancellationToken.None));
288-
Assert.AreEqual(ErrorReason.EnvironmentLayoutClientInstancesNotFound, exception.Reason);
288+
Assert.AreEqual(ErrorReason.LayoutInvalid, exception.Reason);
289289
}
290290
}
291291

@@ -304,7 +304,7 @@ public void NTttcpExecutorThrowsIfAnUnsupportedRoleIsSupplied_Windows(PlatformID
304304
using (TestNTttcpExecutor executor = new TestNTttcpExecutor(this.mockFixture.Dependencies, this.mockFixture.Parameters))
305305
{
306306
var exception = Assert.ThrowsAsync<DependencyException>(() => executor.ExecuteAsync(CancellationToken.None));
307-
Assert.AreEqual(ErrorReason.EnvironmentLayoutClientInstancesNotFound, exception.Reason);
307+
Assert.AreEqual(ErrorReason.LayoutInvalid, exception.Reason);
308308
}
309309
}
310310

@@ -321,7 +321,7 @@ public void NTttcpExecutorExecutesTheExpectedLogicWhenASpecificRoleIsNotDefined_
321321
using (TestNTttcpExecutor executor = new TestNTttcpExecutor(this.mockFixture.Dependencies, this.mockFixture.Parameters))
322322
{
323323
var exception = Assert.ThrowsAsync<DependencyException>(() => executor.ExecuteAsync(CancellationToken.None));
324-
Assert.AreEqual(ErrorReason.EnvironmentLayoutNotDefined, exception.Reason);
324+
Assert.AreEqual(ErrorReason.LayoutNotDefined, exception.Reason);
325325
}
326326
}
327327

@@ -339,7 +339,7 @@ public void NTttcpExecutorExecutesTheExpectedLogicWhenASpecificRoleIsNotDefined_
339339
using (TestNTttcpExecutor executor = new TestNTttcpExecutor(this.mockFixture.Dependencies, this.mockFixture.Parameters))
340340
{
341341
var exception = Assert.ThrowsAsync<DependencyException>(() => executor.ExecuteAsync(CancellationToken.None));
342-
Assert.AreEqual(ErrorReason.EnvironmentLayoutNotDefined, exception.Reason);
342+
Assert.AreEqual(ErrorReason.LayoutNotDefined, exception.Reason);
343343
}
344344
}
345345

src/VirtualClient/VirtualClient.Actions.UnitTests/Network2/SockPerf/SockPerfExecutorTests2.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ public void SockPerfExecutorThrowsIfAnUnsupportedRoleIsSupplied(PlatformID platf
176176
using (TestSockPerfExecutor executor = new TestSockPerfExecutor(this.Dependencies, this.Parameters))
177177
{
178178
var exception = Assert.ThrowsAsync<DependencyException>(() => executor.ExecuteAsync(CancellationToken.None));
179-
Assert.AreEqual(ErrorReason.EnvironmentLayoutClientInstancesNotFound, exception.Reason);
179+
Assert.AreEqual(ErrorReason.LayoutInvalid, exception.Reason);
180180
}
181181
}
182182

@@ -192,7 +192,7 @@ public void SockPerfExecutorExecutesTheExpectedLogicWhenASpecificRoleIsNotDefine
192192
using (TestSockPerfExecutor executor = new TestSockPerfExecutor(this.Dependencies, this.Parameters))
193193
{
194194
var exception = Assert.ThrowsAsync<DependencyException>(() => executor.ExecuteAsync(CancellationToken.None));
195-
Assert.AreEqual(ErrorReason.EnvironmentLayoutNotDefined, exception.Reason);
195+
Assert.AreEqual(ErrorReason.LayoutNotDefined, exception.Reason);
196196
}
197197
}
198198

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
namespace VirtualClient.Contracts
2+
{
3+
using System;
4+
using NUnit.Framework;
5+
6+
[TestFixture]
7+
[Category("Unit")]
8+
public class StatusCodeRegistryTests
9+
{
10+
[Test]
11+
[TestCase(ErrorReason.DependencyDescriptionInvalid, StatusCodeRegistry.StatusCodeBase.ConfigurationError)]
12+
[TestCase(ErrorReason.DuplicateExtensionsFound, StatusCodeRegistry.StatusCodeBase.ConfigurationError)]
13+
[TestCase(ErrorReason.DuplicatePackagesFound, StatusCodeRegistry.StatusCodeBase.ConfigurationError)]
14+
[TestCase(ErrorReason.EnvironmentIsInsufficent, StatusCodeRegistry.StatusCodeBase.ConfigurationError)]
15+
[TestCase(ErrorReason.ExtensionAssemblyInvalid, StatusCodeRegistry.StatusCodeBase.ConfigurationError)]
16+
[TestCase(ErrorReason.InvalidOrMissingLicense, StatusCodeRegistry.StatusCodeBase.ConfigurationError)]
17+
[TestCase(ErrorReason.InvalidProfileDefinition, StatusCodeRegistry.StatusCodeBase.ConfigurationError)]
18+
[TestCase(ErrorReason.LinuxDistributionNotSupported, StatusCodeRegistry.StatusCodeBase.ConfigurationError)]
19+
[TestCase(ErrorReason.NotSupported, StatusCodeRegistry.StatusCodeBase.ConfigurationError)]
20+
[TestCase(ErrorReason.PlatformNotSupported, StatusCodeRegistry.StatusCodeBase.ConfigurationError)]
21+
[TestCase(ErrorReason.ProcessorArchitectureNotSupported, StatusCodeRegistry.StatusCodeBase.ConfigurationError)]
22+
[TestCase(ErrorReason.Unauthorized, StatusCodeRegistry.StatusCodeBase.ConfigurationError)]
23+
[TestCase(ErrorReason.VersionNotSupported, StatusCodeRegistry.StatusCodeBase.ConfigurationError)]
24+
[TestCase(ErrorReason.ApiRequestFailed, StatusCodeRegistry.StatusCodeBase.OrchestrationError)]
25+
[TestCase(ErrorReason.ApiStartupFailed, StatusCodeRegistry.StatusCodeBase.OrchestrationError)]
26+
[TestCase(ErrorReason.ApiStatePollingTimeout, StatusCodeRegistry.StatusCodeBase.OrchestrationError)]
27+
[TestCase(ErrorReason.DependencyInstallationFailed, StatusCodeRegistry.StatusCodeBase.OrchestrationError)]
28+
[TestCase(ErrorReason.DependencyNotFound, StatusCodeRegistry.StatusCodeBase.OrchestrationError)]
29+
[TestCase(ErrorReason.Http400BadRequestResponse, StatusCodeRegistry.StatusCodeBase.OrchestrationError)]
30+
[TestCase(ErrorReason.Http403ForbiddenResponse, StatusCodeRegistry.StatusCodeBase.OrchestrationError)]
31+
[TestCase(ErrorReason.Http404NotFoundResponse, StatusCodeRegistry.StatusCodeBase.OrchestrationError)]
32+
[TestCase(ErrorReason.Http409ConflictResponse, StatusCodeRegistry.StatusCodeBase.OrchestrationError)]
33+
[TestCase(ErrorReason.Http412PreconditionFailedResponse, StatusCodeRegistry.StatusCodeBase.OrchestrationError)]
34+
[TestCase(ErrorReason.HttpNonSuccessResponse, StatusCodeRegistry.StatusCodeBase.OrchestrationError)]
35+
[TestCase(ErrorReason.NetworkTargetDoesNotExist, StatusCodeRegistry.StatusCodeBase.OrchestrationError)]
36+
[TestCase(ErrorReason.WorkloadDependencyMissing, StatusCodeRegistry.StatusCodeBase.OrchestrationError)]
37+
[TestCase(ErrorReason.WorkloadNotFound, StatusCodeRegistry.StatusCodeBase.OrchestrationError)]
38+
[TestCase(ErrorReason.DiskFormatFailed, StatusCodeRegistry.StatusCodeBase.SystemError)]
39+
[TestCase(ErrorReason.DiskInformationNotAvailable, StatusCodeRegistry.StatusCodeBase.SystemError)]
40+
[TestCase(ErrorReason.DiskMountFailed, StatusCodeRegistry.StatusCodeBase.SystemError)]
41+
[TestCase(ErrorReason.FileUploadNotificationCreationFailed, StatusCodeRegistry.StatusCodeBase.SystemError)]
42+
[TestCase(ErrorReason.PerformanceCounterNotFound, StatusCodeRegistry.StatusCodeBase.SystemError)]
43+
[TestCase(ErrorReason.SystemMemoryReadFailed, StatusCodeRegistry.StatusCodeBase.SystemError)]
44+
[TestCase(ErrorReason.SystemOperationFailed, StatusCodeRegistry.StatusCodeBase.SystemError)]
45+
[TestCase(ErrorReason.CriticalWorkloadFailure, StatusCodeRegistry.StatusCodeBase.ToolsetError)]
46+
[TestCase(ErrorReason.InvalidResults, StatusCodeRegistry.StatusCodeBase.ToolsetError)]
47+
[TestCase(ErrorReason.MonitorFailed, StatusCodeRegistry.StatusCodeBase.ToolsetError)]
48+
[TestCase(ErrorReason.MonitorUnexpectedAnomaly, StatusCodeRegistry.StatusCodeBase.ToolsetError)]
49+
[TestCase(ErrorReason.WorkloadFailed, StatusCodeRegistry.StatusCodeBase.ToolsetError)]
50+
[TestCase(ErrorReason.WorkloadResultsNotFound, StatusCodeRegistry.StatusCodeBase.ToolsetError)]
51+
[TestCase(ErrorReason.WorkloadResultsParsingFailed, StatusCodeRegistry.StatusCodeBase.ToolsetError)]
52+
[TestCase(ErrorReason.WorkloadUnexpectedAnomaly, StatusCodeRegistry.StatusCodeBase.ToolsetError)]
53+
[TestCase(ErrorReason.ContentStoreNotDefined, StatusCodeRegistry.StatusCodeBase.UsageError)]
54+
[TestCase(ErrorReason.DiskFilterNotSupported, StatusCodeRegistry.StatusCodeBase.UsageError)]
55+
[TestCase(ErrorReason.InstructionsNotProvided, StatusCodeRegistry.StatusCodeBase.UsageError)]
56+
[TestCase(ErrorReason.InstructionsNotValid, StatusCodeRegistry.StatusCodeBase.UsageError)]
57+
[TestCase(ErrorReason.InvalidCertificate, StatusCodeRegistry.StatusCodeBase.UsageError)]
58+
[TestCase(ErrorReason.LayoutInvalid, StatusCodeRegistry.StatusCodeBase.UsageError)]
59+
[TestCase(ErrorReason.LayoutNotDefined, StatusCodeRegistry.StatusCodeBase.UsageError)]
60+
[TestCase(ErrorReason.PackageStoreNotDefined, StatusCodeRegistry.StatusCodeBase.UsageError)]
61+
[TestCase(ErrorReason.ProfileNotFound, StatusCodeRegistry.StatusCodeBase.UsageError)]
62+
public void StatusCodeRegistryMapsExistingErrorCodesToExpectedStatusCodes(int errorCode, int expectedStatusCodeBase)
63+
{
64+
int expectedStatusCode = expectedStatusCodeBase + errorCode;
65+
int actualStatusCode = StatusCodeRegistry.GetStatusCode(errorCode);
66+
67+
Assert.AreEqual(
68+
expectedStatusCode,
69+
actualStatusCode,
70+
$"Status code does not match. Status code for error/exit code '{errorCode}' is expected to be '{expectedStatusCode}' but was '{actualStatusCode}' instead.");
71+
}
72+
73+
[Test]
74+
[TestCase(1)]
75+
[TestCase(-1)]
76+
[TestCase(12345678)]
77+
[TestCase(-12345678)]
78+
public void StatusCodeRegistryEmitsTheExpectedStatusCodeForUnmappedExitCodes_1(int unmappedExitCode)
79+
{
80+
int expectedStatusCode = StatusCodeRegistry.StatusCodeBase.CodeError + 1;
81+
int actualStatusCode = StatusCodeRegistry.GetStatusCode(unmappedExitCode);
82+
83+
Assert.AreEqual(
84+
expectedStatusCode,
85+
actualStatusCode,
86+
$"Status code does not match. Status code for error/exit code '{unmappedExitCode}' is expected to be '{expectedStatusCode}' but was '{actualStatusCode}' instead.");
87+
}
88+
89+
[Test]
90+
public void StatusCodeRegistryEmitsTheExpectedStatusCodeForUnmappedExitCodes_2()
91+
{
92+
int expectedStatusCode = StatusCodeRegistry.StatusCodeBase.CodeError + 1;
93+
int actualStatusCode = StatusCodeRegistry.GetStatusCode((int)ErrorReason.Undefined);
94+
95+
Assert.AreEqual(
96+
expectedStatusCode,
97+
actualStatusCode,
98+
$"Status code does not match. Status code for undefined error reasons should be mapped as a code error.");
99+
}
100+
101+
[Test]
102+
public void StatusCodeRegistryHasStatusCodesMappedForAllErrorReasons()
103+
{
104+
// Note to the Developer:
105+
// Every VirtualClient.Contracts -> Enumerations.cs (ErrorReason) MUST have a status code mapped to the
106+
// error reason. Status code mappings are defined in the VirtualClient.Contracts -> StatusCodeRegistry.cs
107+
// file.
108+
//
109+
// Additionally note that the default mapping should be StatusCodeBase.CodeError + 1. No specific exit codes
110+
// should be mapped to this particular status code base. Leave it as-is.
111+
112+
ErrorReason[] allErrorReasons = Enum.GetValues<ErrorReason>();
113+
foreach (ErrorReason reason in allErrorReasons)
114+
{
115+
if (reason != ErrorReason.Undefined)
116+
{
117+
int mappedStatusCode = StatusCodeRegistry.GetStatusCode((int)reason);
118+
119+
Assert.IsTrue(
120+
mappedStatusCode > 0,
121+
$"Status Code not mapped correctly for '{reason.ToString()}'.");
122+
123+
Assert.IsTrue(
124+
mappedStatusCode != StatusCodeRegistry.StatusCodeBase.CodeError + 1,
125+
$"Status Code not mapped correctly for '{reason.ToString()}'. ALL code errors should be left as default.");
126+
}
127+
}
128+
}
129+
}
130+
}

src/VirtualClient/VirtualClient.Contracts.UnitTests/VirtualClientComponentExtensionsTests.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -688,7 +688,7 @@ public void VerifyLayoutDefinedExtensionThrowsWhenVerifyingTheEnvironmentLayoutI
688688
this.fixture.Dependencies.RemoveAll<EnvironmentLayout>();
689689

690690
DependencyException error = Assert.Throws<DependencyException>(() => component.ThrowIfLayoutNotDefined());
691-
Assert.AreEqual(ErrorReason.EnvironmentLayoutNotDefined, error.Reason);
691+
Assert.AreEqual(ErrorReason.LayoutNotDefined, error.Reason);
692692
}
693693

694694
[Test]
@@ -700,7 +700,7 @@ public void GetLayoutClientInstanceExtensionThrowsWhenMatchingClientInstancesDoN
700700

701701
// Matching client instance does not exist by agent ID
702702
DependencyException error = Assert.Throws<DependencyException>(() => component.GetLayoutClientInstance("NonExistentAgentID"));
703-
Assert.AreEqual(ErrorReason.EnvironmentLayoutClientInstancesNotFound, error.Reason);
703+
Assert.AreEqual(ErrorReason.LayoutInvalid, error.Reason);
704704
}
705705

706706
[Test]
@@ -712,7 +712,7 @@ public void GetLayoutClientInstancesExtensionThrowsWhenMatchingClientInstancesDo
712712

713713
// Matching client instance(s) not found by role
714714
DependencyException error = Assert.Throws<DependencyException>(() => component.GetLayoutClientInstances("NonExistentAgentRole"));
715-
Assert.AreEqual(ErrorReason.EnvironmentLayoutClientInstancesNotFound, error.Reason);
715+
Assert.AreEqual(ErrorReason.LayoutInvalid, error.Reason);
716716
}
717717

718718
[Test]

src/VirtualClient/VirtualClient.Contracts/Enumerations.cs

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,7 @@ public enum ErrorReason : int
268268
/// <summary>
269269
/// The content store was not defined on the command line.
270270
/// </summary>
271-
ContentStoreNotDefined = 530,
271+
ContentStoreNotDefined = 531,
272272

273273
/// <summary>
274274
/// The Virtual Client API service failed to startup.
@@ -288,28 +288,13 @@ public enum ErrorReason : int
288288
/// <summary>
289289
/// The environment layout is null.
290290
/// </summary>
291-
EnvironmentLayoutNotDefined = 550,
291+
LayoutNotDefined = 550,
292292

293293
/// <summary>
294294
/// Environment layout is not valid or is missing required information.
295295
/// </summary>
296296
LayoutInvalid = 551,
297297

298-
/// <summary>
299-
/// IP address present on layout does not matches with the IPAddress of the machine.
300-
/// </summary>
301-
LayoutIPAddressDoesNotMatch = 552,
302-
303-
/// <summary>
304-
/// The layout does not contain any instance whose name matches with agent id.
305-
/// </summary>
306-
EnvironmentLayoutClientInstancesNotFound = 553,
307-
308-
/// <summary>
309-
/// The layout contains multiple instances whose name matches with agent id.
310-
/// </summary>
311-
EnvironmentLayoutClientInstanceDuplicates = 554,
312-
313298
/// <summary>
314299
/// An extensions assembly is not valid for the current Virtual Client
315300
/// runtime.

src/VirtualClient/VirtualClient.Contracts/Extensions/EnvironmentLayoutExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public static ClientInstance GetClientInstance(this EnvironmentLayout layout, st
2929
throw new DependencyException(
3030
$"Ambiguous environment layout scenario. There is more than one client instance defined in the environment layout " +
3131
$"provided to the Virtual Client for agent/client ID '{clientId}'.",
32-
ErrorReason.EnvironmentLayoutClientInstanceDuplicates);
32+
ErrorReason.LayoutInvalid);
3333
}
3434

3535
return clientInstances?.FirstOrDefault();

0 commit comments

Comments
 (0)