Skip to content

Commit 2fbcbb6

Browse files
author
Kristopher Turner
committed
feat: VMFleet load testing implementation
- Add Prepare-VMFleetBaseImage.ps1 infrastructure provisioning script - Add sequential-throughput.yml workload profile - Add Azure Monitor workbook and KQL queries for VMFleet metrics - Add custom_location_id and storage_path_id to config schema and example - Add Pester tests for VMFleet scripts and base image preparation - Add .psscriptanalyzer.psd1 settings; update PSScriptAnalyzer.ps1 to use it - Add sample output files (aggregate, per-VM, metrics stream) in examples/ - Copy azurelocal-theme.yml to docs/themes/ (required by ReportGenerator.psm1) - Update docs: fix my-cluster.yml references, remove phantom excel-template.xlsx - Update prerequisites.md with azure_local config fields - Expand CHANGELOG with VMFleet deliverables
1 parent c463d71 commit 2fbcbb6

24 files changed

Lines changed: 1633 additions & 25 deletions

.psscriptanalyzer.psd1

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# =============================================================================
2+
# PSScriptAnalyzer Settings — Azure Local Load Tools
3+
# =============================================================================
4+
# Usage: Invoke-ScriptAnalyzer -Path src/ -Settings .psscriptanalyzer.psd1
5+
# =============================================================================
6+
7+
@{
8+
Severity = @('Error', 'Warning')
9+
10+
ExcludeRules = @(
11+
'PSAvoidUsingConvertToSecureStringWithPlainText' # Demo/template code
12+
)
13+
14+
Rules = @{
15+
PSUseCompatibleSyntax = @{
16+
Enable = $true
17+
TargetVersions = @('7.2')
18+
}
19+
20+
PSPlaceOpenBrace = @{
21+
Enable = $true
22+
OnSameLine = $true
23+
NewLineAfter = $true
24+
IgnoreOneLineBlock = $true
25+
}
26+
27+
PSPlaceCloseBrace = @{
28+
Enable = $true
29+
NewLineAfter = $false
30+
IgnoreOneLineBlock = $true
31+
NoEmptyLineBefore = $false
32+
}
33+
34+
PSUseConsistentIndentation = @{
35+
Enable = $true
36+
Kind = 'space'
37+
PipelineIndentation = 'IncreaseIndentationForFirstPipeline'
38+
IndentationSize = 4
39+
}
40+
41+
PSUseConsistentWhitespace = @{
42+
Enable = $true
43+
CheckInnerBrace = $true
44+
CheckOpenBrace = $true
45+
CheckOpenParen = $true
46+
CheckOperator = $true
47+
CheckPipe = $true
48+
CheckPipeForRedundantWhitespace = $false
49+
CheckSeparator = $true
50+
IgnoreAssignmentOperatorInsideHashTable = $true
51+
}
52+
53+
PSAlignAssignmentStatement = @{
54+
Enable = $true
55+
CheckHashtable = $false
56+
}
57+
}
58+
}

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,17 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
1313
- Monitoring collectors, dashboards, and alert rules
1414
- Report templates for performance analysis
1515

16+
### VMFleet
17+
18+
- Add `Prepare-VMFleetBaseImage.ps1` — automates WS2022 Core Gen2 base image download from Azure Local marketplace
19+
- Add `sequential-throughput.yml` workload profile — 512K sequential writes for MB/s ceiling measurement
20+
- Add Azure Monitor workbook (`vmfleet-workbook.json`) with IOPS, latency P95, and throughput panels
21+
- Add KQL queries for Log Analytics (`vmfleet-iops.kql`, `vmfleet-latency.kql`)
22+
- Add `custom_location_id` and `storage_path_id` fields to config schema and example
23+
- Add Pester tests for VMFleet script standards compliance and base image script
24+
- Add PSScriptAnalyzer settings file (`.psscriptanalyzer.psd1`)
25+
- Expand VMFleet documentation: prerequisites, deployment, workload profiles, monitoring, reporting, troubleshooting
26+
1627
### Infrastructure
1728

1829
- Add GitHub Actions workflows for docs, testing, and CI
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# =============================================================================
2+
# VMFleet Workload Profile: Sequential Throughput
3+
# =============================================================================
4+
# Large-block sequential writes for maximum throughput (MB/s) measurement.
5+
# Tests the raw bandwidth ceiling of Storage Spaces Direct.
6+
# =============================================================================
7+
8+
profile:
9+
name: "Sequential Throughput"
10+
description: "512K sequential writes for maximum storage throughput measurement"
11+
category: "throughput"
12+
parameters:
13+
block_size: "512k"
14+
write_ratio: 100
15+
random_ratio: 0
16+
outstanding_io: 32
17+
threads_per_vm: 2
18+
duration_seconds: 300
19+
warmup_seconds: 60
20+
expected_thresholds:
21+
min_iops_per_node: 500
22+
max_latency_ms: 50
23+
min_throughput_mbps: 2000

config/schema/variables.schema.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,12 @@
2626
},
2727
"azure_local": {
2828
"type": "object",
29-
"required": ["cluster_name", "nodes"],
29+
"required": ["cluster_name", "custom_location_id", "storage_path_id", "nodes"],
3030
"properties": {
3131
"cluster_name": { "type": "string" },
3232
"cluster_domain": { "type": "string" },
33+
"custom_location_id": { "type": "string" },
34+
"storage_path_id": { "type": "string" },
3335
"nodes": {
3436
"type": "array",
3537
"minItems": 1,

config/variables.example.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ keyvault:
4444
azure_local:
4545
cluster_name: "azl-cluster-01"
4646
cluster_domain: "iic.local"
47+
custom_location_id: "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.ExtendedLocation/customLocations/azl-cluster-01-cl"
48+
storage_path_id: "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.AzureStackHCI/storageContainers/sp-vmstore-01"
4749
nodes:
4850
- name: "azl-node-01"
4951
management_ip: "10.0.0.1"

docs/getting-started/prerequisites.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ Required only if using Azure Monitor integration or Azure Key Vault for credenti
5656
| Azure Key Vault | Provisioned Key Vault with secrets for cluster credentials |
5757
| Log Analytics Workspace | Provisioned workspace for Azure Monitor metric ingestion |
5858
| Service Principal or Managed Identity | For non-interactive Azure authentication in CI/CD |
59+
| Azure Local custom location | Required for marketplace image acquisition (`azure_local.custom_location_id`) |
60+
| Azure Local storage container | Required to resolve VHDX path (`azure_local.storage_path_id`) |
5961

6062
## Validating Prerequisites
6163

docs/themes/azurelocal-theme.yml

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
# =============================================================================
2+
# Azure Local Load Tools - Custom PDF Theme for asciidoctor-pdf
3+
# =============================================================================
4+
# Usage: asciidoctor-pdf -a pdf-theme=azurelocal-theme docs/main.adoc
5+
extends: default
6+
font:
7+
catalog:
8+
merge: true
9+
page:
10+
margin: [0.75in, 0.75in, 1in, 0.75in]
11+
size: A4
12+
layout: portrait
13+
base:
14+
font-family: Noto Sans
15+
font-size: 10
16+
font-color: #333333
17+
line-height-length: 15
18+
line-height: $base_line_height_length / $base_font_size
19+
heading:
20+
font-color: #0078D4
21+
font-style: bold
22+
h1-font-size: 24
23+
h2-font-size: 20
24+
h3-font-size: 16
25+
h4-font-size: 14
26+
margin-top: 15
27+
margin-bottom: 10
28+
title-page:
29+
align: center
30+
font-color: #0078D4
31+
title:
32+
top: 40%
33+
font-size: 32
34+
font-style: bold
35+
font-color: #0078D4
36+
line-height: 1.2
37+
subtitle:
38+
font-size: 18
39+
font-color: #505050
40+
margin-top: 10
41+
authors:
42+
font-size: 14
43+
font-color: #505050
44+
margin-top: 20
45+
revision:
46+
font-size: 12
47+
font-color: #808080
48+
margin-top: 10
49+
header:
50+
height: 0.5in
51+
font-size: 8
52+
font-color: #808080
53+
border-color: #DDDDDD
54+
border-width: 0.5
55+
recto:
56+
right:
57+
content: '{document-title}'
58+
verso:
59+
left:
60+
content: '{document-title}'
61+
footer:
62+
height: 0.5in
63+
font-size: 8
64+
font-color: #808080
65+
border-color: #DDDDDD
66+
border-width: 0.5
67+
recto:
68+
center:
69+
content: 'Page {page-number} of {page-count}'
70+
right:
71+
content: '{revdate}'
72+
verso:
73+
center:
74+
content: 'Page {page-number} of {page-count}'
75+
left:
76+
content: '{revdate}'
77+
code:
78+
font-family: Noto Sans Mono
79+
font-size: 9
80+
background-color: #F5F5F5
81+
border-color: #DDDDDD
82+
border-width: 0.5
83+
padding: [8, 10, 8, 10]
84+
table:
85+
head:
86+
font-color: #FFFFFF
87+
background-color: #0078D4
88+
font-style: bold
89+
body:
90+
stripe-background-color: #F0F6FF
91+
border-color: #CCCCCC
92+
border-width: 0.5
93+
admonition:
94+
icon:
95+
note:
96+
stroke-color: #0078D4
97+
tip:
98+
stroke-color: #107C10
99+
warning:
100+
stroke-color: #FF8C00
101+
caution:
102+
stroke-color: #D83B01
103+
important:
104+
stroke-color: #A4262C
105+
sidebar:
106+
background-color: #F0F6FF
107+
border-color: #0078D4
108+
border-width: 1
109+
toc:
110+
dot-leader:
111+
font-color: #CCCCCC
112+
indent: 20
113+
line-height: 1.8

docs/tools/vmfleet/deployment.md

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,25 @@
55

66
This guide covers installing the VMFleet module and deploying fleet VMs across your Azure Local cluster.
77

8+
## Phase 0: Prepare Base Image (first run only)
9+
10+
Before deployment, prepare the base image from Azure Local marketplace (or verify it already exists):
11+
12+
```powershell
13+
.\src\infrastructure\Prepare-VMFleetBaseImage.ps1 `
14+
-ConfigPath "config/variables.yml" `
15+
-ClusterConfigPath "config/clusters/my-cluster.yml"
16+
```
17+
18+
After completion, set the resolved VHDX path in `storage.base_vhd_path` in `config/variables.yml`.
19+
820
## Install VMFleet
921

1022
Installs the VMFleet PowerShell module and prepares the cluster:
1123

1224
```powershell
1325
.\src\solutions\vmfleet\scripts\Install-VMFleet.ps1 `
14-
-ClusterConfig "config/clusters/my-cluster.yml" `
15-
-CredentialSource Interactive
26+
-ClusterConfigPath "config/clusters/my-cluster.yml"
1627
```
1728

1829
## Deploy Fleet VMs
@@ -21,10 +32,10 @@ Creates fleet VMs across all cluster nodes:
2132

2233
```powershell
2334
.\src\solutions\vmfleet\scripts\Deploy-VMFleet.ps1 `
24-
-ClusterConfig "config/clusters/my-cluster.yml" `
25-
-VmCountPerNode 10 `
26-
-VmVcpuCount 2 `
27-
-VmMemoryGb 2
35+
-ClusterConfigPath "config/clusters/my-cluster.yml" `
36+
-VMCount 10 `
37+
-VMProcessorCount 2 `
38+
-VMMemoryGB 2
2839
```
2940

3041
The number of VMs, vCPUs, and memory per VM are configurable via:
@@ -41,11 +52,16 @@ To remove fleet VMs and restore the cluster to its pre-test state:
4152

4253
```powershell
4354
.\src\solutions\vmfleet\scripts\Remove-VMFleet.ps1 `
44-
-ClusterConfig "config/clusters/my-cluster.yml" `
45-
-CredentialSource Interactive `
55+
-ClusterConfigPath "config/clusters/my-cluster.yml" `
4656
-Confirm
4757
```
4858

59+
## First-Time vs Subsequent Runs
60+
61+
- First-time environment bootstrap: run Phase 0 image prep once, then Install and Deploy.
62+
- Subsequent test cycles: skip Phase 0 unless you want to force-refresh image download.
63+
- Day-2 operations: run test, monitor, collect, report, and optional cleanup.
64+
4965
## Next Steps
5066

5167
- [Workload Profiles](workload-profiles.md) — Run test workloads

docs/tools/vmfleet/monitoring.md

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,19 @@ PowerShell Get-Counter (remote sessions)
2121
Azure Portal dashboards, alerts
2222
```
2323

24+
## Live Console Monitoring
25+
26+
Use the built-in watcher during active tests:
27+
28+
```powershell
29+
.\src\solutions\vmfleet\scripts\Watch-VMFleetMonitor.ps1 `
30+
-ClusterConfigPath "config/clusters/my-cluster.yml" `
31+
-RefreshIntervalSeconds 5 `
32+
-DurationMinutes 30
33+
```
34+
35+
The watcher prints rolling VM count, read/write IOPS, read/write latency, and throughput.
36+
2437
## Metric Categories
2538

2639
### Storage Metrics
@@ -105,6 +118,15 @@ Optionally push collected metrics to Azure Monitor for centralized dashboards an
105118
-CredentialSource KeyVault
106119
```
107120

121+
Each pushed metric contains these core fields:
122+
123+
- `timestamp`
124+
- `node`
125+
- `counter_name`
126+
- `value`
127+
- `run_id`
128+
- `profile_name`
129+
108130
!!! note
109131
Azure Monitor integration requires the `monitoring.bicep` infrastructure template to be deployed. See `src/infrastructure/bicep/monitoring.bicep`.
110132

@@ -134,6 +156,37 @@ During test execution, use the monitoring dashboard wrapper:
134156
```powershell
135157
# Launch real-time monitoring (combines VMFleet Watch-FleetCluster with custom metrics)
136158
.\src\solutions\vmfleet\monitoring\Export-MetricsDashboard.ps1 `
137-
-ClusterConfig "config/clusters/my-cluster.yml" `
138-
-RefreshIntervalSeconds 5
159+
-InputPath "results/run-001/metrics/" `
160+
-OutputPath "reports/run-001/" `
161+
-Title "VMFleet Run 001"
162+
```
163+
164+
## Counter Reference
165+
166+
| Category | Counter name | Purpose |
167+
| --- | --- | --- |
168+
| Storage | `CSVFS_ReadIOPS` | CSVFS read operations per second |
169+
| Storage | `CSVFS_WriteIOPS` | CSVFS write operations per second |
170+
| Storage | `CSVFS_ReadMBps` | Read throughput |
171+
| Storage | `CSVFS_WriteMBps` | Write throughput |
172+
| Storage | `CSVFS_ReadLatencyMs` | Read latency |
173+
| Storage | `CSVFS_WriteLatencyMs` | Write latency |
174+
| Compute | `HostCpuPercent` | Host CPU saturation |
175+
| Compute | `HostAvailableMemoryMB` | Available memory headroom |
176+
| Compute | `HyperVLogicalProcessorRunTime` | Hypervisor CPU pressure |
177+
178+
## KQL Examples
179+
180+
```kusto
181+
VMFleetMetrics_CL
182+
| where CounterName_s in ("CSVFS_ReadIOPS", "CSVFS_WriteIOPS")
183+
| summarize AvgValue=avg(Value_d) by bin(TimeGenerated, 1m), CounterName_s
184+
| render timechart
185+
```
186+
187+
```kusto
188+
VMFleetMetrics_CL
189+
| where CounterName_s in ("CSVFS_ReadLatencyMs", "CSVFS_WriteLatencyMs")
190+
| summarize P95=percentile(Value_d, 95) by bin(TimeGenerated, 1m), CounterName_s
191+
| render timechart
139192
```

0 commit comments

Comments
 (0)