-
Notifications
You must be signed in to change notification settings - Fork 31
/
Example2WorkloadExecutor.cs
249 lines (217 loc) · 10.3 KB
/
Example2WorkloadExecutor.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace VirtualClient.Actions
{
using System;
using System.Collections.Generic;
using System.IO.Abstractions;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using VirtualClient.Common;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using VirtualClient.Contracts;
using VirtualClient.Common.Extensions;
using VirtualClient.Common.Telemetry;
/// <summary>
/// This is an example workload that is used to illustrate testing patterns
/// for both unit and functional tests.
/// </summary>
public class Example2WorkloadExecutor : VirtualClientComponent
{
private IFileSystem fileSystem;
private IPackageManager packageManager;
private ProcessManager processManager;
private IStateManager stateManager;
private ISystemManagement systemManagement;
/// <summary>
/// Initializes a new instance of the <see cref="ExampleWorkloadExecutor"/> class.
/// </summary>
/// <param name="dependencies">Provides required dependencies to the component.</param>
/// <param name="parameters">Parameters defined in the profile or supplied on the command line.</param>
public Example2WorkloadExecutor(IServiceCollection dependencies, IDictionary<string, IConvertible> parameters)
: base(dependencies, parameters)
{
this.systemManagement = dependencies.GetService<ISystemManagement>();
this.fileSystem = this.systemManagement.FileSystem;
this.packageManager = this.systemManagement.PackageManager;
this.processManager = this.systemManagement.ProcessManager;
this.stateManager = this.systemManagement.StateManager;
}
/// <summary>
/// Parameter defines the command line arguments to pass to the workload executable.
/// </summary>
public string CommandLine
{
get
{
return this.Parameters.GetValue<string>(nameof(this.CommandLine));
}
}
/// <summary>
/// Parameter indicates the name of the test.
/// </summary>
public string TestName
{
get
{
return this.Parameters.GetValue<string>(nameof(this.TestName));
}
}
/// <summary>
/// The path to the workload executable.
/// </summary>
protected string ExecutablePath { get; set; }
/// <summary>
/// The path to the results file (if defined).
/// </summary>
protected string ResultsFilePath { get; set; }
/// <summary>
/// The workload state object ID.
/// </summary>
protected string StateId
{
get
{
return $"{nameof(Example2WorkloadExecutor)}-state";
}
}
/// <summary>
/// Mimics a requirement of performing some system settings update that requires a reboot.
/// </summary>
protected async Task ApplySystemSettingsAsync(CancellationToken cancellationToken)
{
string configurationCommand = this.Platform == PlatformID.Win32NT
? "configureSystem.exe"
: "configureSystem";
using (IProcessProxy process = this.processManager.CreateElevatedProcess(this.Platform, configurationCommand))
{
await process.StartAndWaitAsync(cancellationToken, TimeSpan.FromSeconds(10))
.ConfigureAwait(false);
process.ThrowIfErrored<WorkloadException>(ProcessProxy.DefaultSuccessCodes, errorReason: ErrorReason.WorkloadFailed);
}
}
/// <summary>
/// Executes the workload.
/// </summary>
protected override async Task ExecuteAsync(EventContext telemetryContext, CancellationToken cancellationToken)
{
// This is just to illustrate aspects of the workload that require saving state (e.g. to the file system).
// Workloads that have to communicate between different instances of the Virtual Client often use state to
// communicate (e.g. client/server interactions).
WorkloadState workloadState = await this.stateManager.GetStateAsync<WorkloadState>(this.StateId, cancellationToken)
.ConfigureAwait(false);
if (workloadState == null)
{
// Mimics a requirement of performing some system settings update that requires a reboot.
await this.ApplySystemSettingsAsync(cancellationToken)
.ConfigureAwait(false);
await this.stateManager.SaveStateAsync<WorkloadState>(this.StateId, new WorkloadState { IsFirstRun = false }, cancellationToken)
.ConfigureAwait(false);
// A reboot happens out-of-band from the executor to ensure all threads/processes running have a
// chance to exit gracefully and for telemetry to be fully emitted from off of the system before
// processing the actual reboot request.
this.RequestReboot();
}
else
{
await this.ExecuteWorkloadAsync(telemetryContext, cancellationToken)
.ConfigureAwait(false);
}
}
/// <summary>
/// Initializes the workload dependencies and requirements.
/// </summary>
protected override async Task InitializeAsync(EventContext telemetryContext, CancellationToken cancellationToken)
{
// Almost all workload executors access the workloads via structure packages.
DependencyPath workloadPackage = await this.InitializeWorkloadPackageAsync(telemetryContext, cancellationToken);
await this.InitializeWorkloadExecutablesAsync(workloadPackage, telemetryContext, cancellationToken);
}
private void CaptureMetrics(IProcessProxy process, DateTime startTime, DateTime endTime, EventContext telemetryContext)
{
string results = process.StandardOutput.ToString();
Example2WorkloadMetricsParser resultsParser = new Example2WorkloadMetricsParser(results);
IList<Metric> metrics = resultsParser.Parse();
this.Logger.LogMetrics(
"SomeWorkload",
this.TestName,
startTime,
endTime,
metrics,
null,
this.CommandLine,
this.Tags,
telemetryContext);
}
private async Task ExecuteWorkloadAsync(EventContext telemetryContext, CancellationToken cancellationToken)
{
using (IProcessProxy workload = this.processManager.CreateProcess(this.ExecutablePath, this.CommandLine))
{
DateTime startTime = DateTime.UtcNow;
await workload.StartAndWaitAsync(cancellationToken).ConfigureAwait(false);
DateTime endTime = DateTime.UtcNow;
await this.LogProcessDetailsAsync(workload, telemetryContext);
workload.ThrowIfErrored<WorkloadException>(ProcessProxy.DefaultSuccessCodes, errorReason: ErrorReason.WorkloadFailed);
this.CaptureMetrics(workload, startTime, endTime, telemetryContext);
}
}
private async Task<DependencyPath> InitializeWorkloadPackageAsync(EventContext telemetryContext, CancellationToken cancellationToken)
{
DependencyPath workloadPackage = await this.packageManager.GetPackageAsync(this.PackageName, cancellationToken)
.ConfigureAwait(false);
telemetryContext.AddContext("workloadPackage", workloadPackage);
if (workloadPackage == null)
{
throw new DependencyException(
$"The expected package '{this.PackageName}' does not exist on the system or is not registered.",
ErrorReason.WorkloadDependencyMissing);
}
return this.ToPlatformSpecificPath(workloadPackage, this.Platform, this.CpuArchitecture);
}
private async Task InitializeWorkloadExecutablesAsync(DependencyPath workloadPackage, EventContext telemetryContext, CancellationToken cancellationToken)
{
// In this example, there is a main executable but it uses a set of additional
// executables during execution. On Linux systems, all of these need to be attributed
// as executable.
List<string> executablePaths = null;
switch (this.Platform)
{
case PlatformID.Unix:
executablePaths = new List<string>
{
this.Combine(workloadPackage.Path, "SomeWorkload"),
this.Combine(workloadPackage.Path, "SomeTool1"),
this.Combine(workloadPackage.Path, "SomeTool2")
};
break;
case PlatformID.Win32NT:
executablePaths = new List<string>
{
this.Combine(workloadPackage.Path, "SomeWorkload.exe"),
this.Combine(workloadPackage.Path, "SomeTool1.exe"),
this.Combine(workloadPackage.Path, "SomeTool2.exe")
};
break;
}
telemetryContext.AddContext("workloadExecutables", executablePaths);
foreach (string path in executablePaths)
{
if (!this.fileSystem.File.Exists(path))
{
throw new WorkloadException($"Required workload binary/executable does not exist at the path '{path}'");
}
await this.systemManagement.MakeFileExecutableAsync(path, this.Platform, cancellationToken)
.ConfigureAwait(false);
}
// We purposefully placed the main workload executable first in the list.
this.ExecutablePath = executablePaths.First();
}
internal class WorkloadState
{
[JsonProperty(PropertyName = "isFirstRun")]
public bool IsFirstRun { get; set; }
}
}
}