Merge pull request #13 from microsoft/features/JMeterPipelineEnhancements

JMeter pipeline enhancements
This commit is contained in:
mridul verma 2021-09-21 14:50:10 +05:30 коммит произвёл GitHub
Родитель 9924db58d5 b97d2b358a
Коммит df0baf64ee
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
20 изменённых файлов: 907 добавлений и 198 удалений

4
.gitignore поставляемый
Просмотреть файл

@ -53,6 +53,10 @@ nunit-*.xml
[Rr]eleasePS/
dlldata.c
#LoadTestResults
report/
Reports/
# Benchmark Results
BenchmarkDotNet.Artifacts/

Двоичные данные
Images/JMeter-files.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 7.6 KiB

Двоичные данные
Images/folder-structure.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 52 KiB

Двоичные данные
Images/pipeline-parameters.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 16 KiB

Двоичные данные
Images/pipeline-variables-infra.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 26 KiB

Двоичные данные
Images/pipeline-variables.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 33 KiB

Двоичные данные
Images/run-test-locally.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 204 KiB

Просмотреть файл

@ -1,54 +0,0 @@
jobs:
- job: Region_1
displayName: Performance Agent Instance 1
pool:
vmImage: vs2017-win2016
variables:
- group: PerformanceVariableGroup
steps:
- checkout: self
- task: PowerShell@2
displayName: Update JMX File Parameters
inputs:
targetType: inline
script: "# Change jmeter parameters\n\nsed -i 's/RESOURCE_ID/$(PerfTestResourceId)/g' $(JmeterFolderPath)/$(JmeterFileName)\n\nsed -i 's/CLIENT_ID/$(PerfTestClientId)/g' $(JmeterFolderPath)/$(JmeterFileName)\n\nsed -i 's/CLIENT_SECRET/$(PerfTestClientSecret)/g' $(JmeterFolderPath)/$(JmeterFileName)\n\nsed -i 's/RAMP_TIME/$(RAMP_TIME)/g' $(JmeterFolderPath)/$(JmeterFileName)\n \nsed -i 's/DURATION/$(DURATION)/g' $(JmeterFolderPath)/$(JmeterFileName)\n\nsed -i 's/LOOPS/$(LOOPS)/g' $(JmeterFolderPath)/$(JmeterFileName)\n\nsed -i 's/THREADS/$(THREADS)/g' $(JmeterFolderPath)/$(JmeterFileName)\n"
- task: PowerShell@2
displayName: Run Performance in Region1
inputs:
targetType: filePath
filePath: ./JMeterAKSPipeline/ExecuteInAKS.ps1
arguments: '-AKSClusterName $(AKSClusterNameRegion1) -ResourceGroup $(AKSResourceGroupRegion1) -SPNClientId $(AKSSPNClientIdRegion1) -SPNClientSecret $(AKSSPNClientSecretRegion1) -TenantId $(TenantId) -Namespace $(Namespace) -JMeterFolderPath $(JmeterFolderPath) -JMeterFileName $(JmeterFileName) -CSVFileNames "$(CSVFileNames)"'
pwsh: true
- task: PublishPipelineArtifact@1
displayName: Publish Pipeline Artifact
continueOnError: True
inputs:
path: $(JmeterFolderPath)/$(AKSClusterNameRegion1)
artifactName: Results-Region1-$(THREADS)-$(DURATION)
- job: Region_2
displayName: Performance Agent Instance 2
pool:
vmImage: vs2017-win2016
variables:
- group: PerfVariableGroup
steps:
- checkout: self
- task: PowerShell@2
displayName: Update Performance parameters
inputs:
targetType: inline
script: "# Change jmeter parameters\n\nsed -i 's/RESOURCE_ID/$(PerfTestResourceId)/g' $(JmeterFolderPath)/$(JmeterFileName)\n\nsed -i 's/CLIENT_ID/$(PerfTestClientId)/g' $(JmeterFolderPath)/$(JmeterFileName)\n\nsed -i 's/CLIENT_SECRET/$(PerfTestClientSecret)/g' $(JmeterFolderPath)/$(JmeterFileName)\n\nsed -i 's/RAMP_TIME/$(RAMP_TIME)/g' $(JmeterFolderPath)/$(JmeterFileName)\n \nsed -i 's/DURATION/$(DURATION)/g' $(JmeterFolderPath)/$(JmeterFileName)\n\nsed -i 's/LOOPS/$(LOOPS)/g' $(JmeterFolderPath)/$(JmeterFileName)\n\nsed -i 's/THREADS/$(THREADS)/g' $(JmeterFolderPath)/$(JmeterFileName)\n"
- task: PowerShell@2
displayName: Run Performance in Region2
inputs:
targetType: filePath
filePath: ./JMeterAKSPipeline/ExecuteInAKS.ps1
arguments: '-AKSClusterName $(AKSClusterNameRegion2) -ResourceGroup $(AKSResourceGroupRegion2) -SPNClientId $(AKSSPNClientIdRegion2) -SPNClientSecret $(AKSSPNClientSecretRegion2) -TenantId $(TenantId) -Namespace $(Namespace) -JMeterFolderPath $(JmeterFolderPath) -JMeterFileName $(JmeterFileName) -CSVFileNames "$(CSVFileNames)"'
pwsh: true
- task: PublishPipelineArtifact@1
displayName: Publish Pipeline Artifact
continueOnError: True
inputs:
path: $(JmeterFolderPath)/$(AKSClusterNameRegion2)
artifactName: Results-Region2-$(THREADS)-$(DURATION)
...

Просмотреть файл

@ -0,0 +1,48 @@
param(
[Parameter(Mandatory = $True)]
[string]$tenant,
[Parameter(Mandatory = $True)]
[string]$defaultNamespace,
[Parameter(Mandatory = $True)]
[string]$resourceGroup,
[Parameter(Mandatory = $True)]
[string]$aksClusterName,
[Parameter(Mandatory = $True)]
[string]$spnClientId,
[Parameter(Mandatory = $True)]
[string]$spnClientSecret,
[Parameter(Mandatory = $True)]
[string]$aksRegion,
[Parameter(Mandatory = $False)]
[string]$nodeVmSize = "Standard_D2s_v3"
)
function log([string] $message, [string] $color) {
Write-Host "$(get-date) $message" -ForegroundColor $color
Write-Host " "
}
#################### Creating AKS Cluster ################################################
try {
log "############# Installing Extension : aks-preview ###############" "DarkYellow"
az extension add --name aks-preview
az login --service-principal --username $spnClientId --password $spnClientSecret --tenant $tenant
log "############# AKS Cluster Name : $($aksClusterName) ###############" "DarkMagenta"
log "############# Creating AKS Cluster ###############" "DarkYellow"
az aks create --resource-group $resourceGroup --name $aksClusterName --node-vm-size $nodeVmSize --location $aksRegion --service-principal $spnClientId --client-secret $spnClientSecret --node-count 3 --min-count 1 --max-count 50 --enable-cluster-autoscaler --enable-aad --enable-azure-rbac --generate-ssh-keys
log "############# AKS Cluster creation completed ###############" "DarkGreen"
log "############# Getting cluster credentials #############" "DarkYellow"
az aks get-credentials --name $aksClusterName --resource-group $resourceGroup --admin
log "############# Creating AKS Namespace #############" "DarkYellow"
kubectl create namespace $defaultNamespace
log "AKS cluster $aksClusterName created successfully." "GREEN"
}
catch {
Write-Error "An error occurred while creating Load Test Infrastructure"
Write-Host $_.ScriptStackTrace
}

Просмотреть файл

@ -0,0 +1,43 @@
param(
[Parameter(Mandatory = $True)]
[string]$aksClusterName,
[Parameter(Mandatory = $True)]
[string]$resourceGroup,
[Parameter(Mandatory = $True)]
[string]$spnClientId,
[Parameter(Mandatory = $True)]
[string]$spnClientSecret,
[Parameter(Mandatory = $True)]
[string]$tenant
)
Set-StrictMode -Version latest
$ErrorActionPreference = "Stop"
function log([string] $message, [string] $color) {
Write-Host "$(get-date) $message" -ForegroundColor $color
Write-Host " "
}
try {
if ($(az account list).contains("[]")) {
Exit-PSSession
}
az login --service-principal --username $spnClientId --password $spnClientSecret --tenant $tenant
log "########### Deleting AKS Instance ###########" "DarkYellow"
az aks delete --name $aksClusterName --resource-group $resourceGroup -y
log "############# AKS Instance deleted ###############" "DarkGreen"
log "########### Removing local auth ###########" "DarkYellow"
kubectl config use-context docker-desktop
kubectl config delete-context $aksClusterName
kubectl config delete-cluster $aksClusterName
kubectl config unset "users.clusterUser_$($aksClusterName)_$($aksClusterName)"
log "AKS cluster $aksClusterName deleted successfully." "GREEN"
}
catch {
Write-Error "An error occurred while deleting Load Test Infrastructure"
Write-Host $_.ScriptStackTrace
}

Просмотреть файл

@ -1,98 +1,98 @@
[CmdletBinding()]
param(
[Parameter(Mandatory=$True)]
[string]$AKSClusterName,
[Parameter(Mandatory=$True)]
[string]$ResourceGroup,
[Parameter(Mandatory=$True)]
[string]$SPNClientId,
[Parameter(Mandatory=$True)]
[string]$SPNClientSecret,
[Parameter(Mandatory=$True)]
[string]$TenantId,
[Parameter(Mandatory=$True)]
[string]$Namespace,
[Parameter(Mandatory=$True)]
[string]$JMeterFolderPath,
[Parameter(Mandatory=$True)]
[string]$JMeterFileName,
[string]$CSVFileNames
)
$AKSClusterNames = New-Object Collections.Generic.List[String]
$AKSClusterNames.Add($AKSClusterName);
$ResourceGroups = New-Object Collections.Generic.List[String]
$ResourceGroups.Add($ResourceGroup);
$ClientIds = New-Object Collections.Generic.List[String]
$ClientIds.Add($SPNClientId);
$ClientSecrets = New-Object Collections.Generic.List[String]
$ClientSecrets.Add($SPNClientSecret);
for ($counter=0; $counter -lt $AKSClusterNames.Count; $counter++){
az login --service-principal --username $ClientIds[$counter] --password $ClientSecrets[$counter] --tenant $TenantId
az aks get-credentials --resource-group $ResourceGroups[$counter] --name $AKSClusterNames[$counter] --overwrite --admin
}
$AKSClusterNames | ForEach-Object {
$currentcontext = $_ + "-admin"
$rootDirectory = Get-Location
cd "jmeter-kubernetes-setup"
kubectl create namespace $Namespace --context $currentcontext
kubectl create -n $Namespace -f jmeter_slaves_deploy.yaml --context $currentcontext
kubectl create -n $Namespace -f jmeter_slaves_svc.yaml --context $currentcontext
kubectl create -n $namespace -f jmeter_master_configmap.yaml --context $currentcontext
kubectl create -n $Namespace -f jmeter_master_deploy.yaml --context $currentcontext
kubectl get -n $Namespace all --context $currentcontext
$masternode = kubectl get pods -n $Namespace -o name --context $currentcontext | findstr jmeter-master
$slaves = kubectl get pods -n $Namespace -o name --context $currentcontext | findstr jmeter-slaves
$masternode = $masternode.Substring(4)
cd $rootDirectory
cd $JMeterFolderPath
kubectl cp $JMeterFileName -n $Namespace "${masternode}:/" --context $currentcontext
$CSVFileNamesList = $CSVFileNames -split ","
foreach ($slave in $slaves) {
$slave = $slave.Substring(4)
foreach ($CSVFileName in $CSVFileNamesList){
kubectl cp $CSVFileName -n $Namespace ${slave}:/ --context $currentcontext
}
}
kubectl get namespace --context $currentcontext
kubectl exec -ti -n $Namespace $masternode --context $currentcontext -- /bin/bash /load_test $JMeterFileName
New-Item $AKSClusterName -ItemType "directory"
kubectl cp $Namespace/${masternode}:report/ $AKSClusterName --context $currentcontext
kubectl delete namespace $Namespace --context $currentcontext
}
[CmdletBinding()]
param(
[Parameter(Mandatory=$True)]
[string]$AKSClusterName,
[Parameter(Mandatory=$True)]
[string]$ResourceGroup,
[Parameter(Mandatory=$True)]
[string]$SPNClientId,
[Parameter(Mandatory=$True)]
[string]$SPNClientSecret,
[Parameter(Mandatory=$True)]
[string]$TenantId,
[Parameter(Mandatory=$True)]
[string]$Namespace,
[Parameter(Mandatory=$True)]
[string]$JMeterFolderPath,
[Parameter(Mandatory=$True)]
[string]$JMeterFileName,
[string]$CSVFileNames
)
$AKSClusterNames = New-Object Collections.Generic.List[String]
$AKSClusterNames.Add($AKSClusterName);
$ResourceGroups = New-Object Collections.Generic.List[String]
$ResourceGroups.Add($ResourceGroup);
$ClientIds = New-Object Collections.Generic.List[String]
$ClientIds.Add($SPNClientId);
$ClientSecrets = New-Object Collections.Generic.List[String]
$ClientSecrets.Add($SPNClientSecret);
for ($counter=0; $counter -lt $AKSClusterNames.Count; $counter++){
az login --service-principal --username $ClientIds[$counter] --password $ClientSecrets[$counter] --tenant $TenantId
az aks get-credentials --resource-group $ResourceGroups[$counter] --name $AKSClusterNames[$counter] --overwrite --admin
}
$AKSClusterNames | ForEach-Object {
$currentcontext = $_ + "-admin"
$rootDirectory = Get-Location
cd "jmeter-kubernetes-setup"
kubectl create namespace $Namespace --context $currentcontext
kubectl create -n $Namespace -f jmeter_slaves_deploy.yaml --context $currentcontext
kubectl create -n $Namespace -f jmeter_slaves_svc.yaml --context $currentcontext
kubectl create -n $namespace -f jmeter_master_configmap.yaml --context $currentcontext
kubectl create -n $Namespace -f jmeter_master_deploy.yaml --context $currentcontext
kubectl get -n $Namespace all --context $currentcontext
$masternode = kubectl get pods -n $Namespace -o name --context $currentcontext | findstr jmeter-master
$slaves = kubectl get pods -n $Namespace -o name --context $currentcontext | findstr jmeter-slaves
$masternode = $masternode.Substring(4)
cd $rootDirectory
cd $JMeterFolderPath
kubectl cp $JMeterFileName -n $Namespace "${masternode}:/" --context $currentcontext
$CSVFileNamesList = $CSVFileNames -split ","
foreach ($slave in $slaves) {
$slave = $slave.Substring(4)
foreach ($CSVFileName in $CSVFileNamesList){
kubectl cp $CSVFileName -n $Namespace ${slave}:/ --context $currentcontext
}
}
kubectl get namespace --context $currentcontext
kubectl exec -ti -n $Namespace $masternode --context $currentcontext -- /bin/bash /load_test $JMeterFileName
New-Item $AKSClusterName -ItemType "directory"
kubectl cp $Namespace/${masternode}:report/ $AKSClusterName --context $currentcontext
kubectl delete namespace $Namespace --context $currentcontext
}

Просмотреть файл

@ -0,0 +1,32 @@
pool:
vmImage: "windows-latest"
trigger: none
# variables:
# DefaultNamespace: ""
# AksClusterName: ""
# ResourceGroup: ""
# ServicePrincipalId: ""
# AksRegion: ""
# NodeVmSize: ""
# ServiceConnection: ""
# KeyVaultName: ""
# SecretName: ""
# Tenant: ""
steps:
- task: AzureKeyVault@1
displayName: Get Keyvault Secrets
inputs:
azureSubscription: $(ServiceConnection)
KeyVaultName: $(KeyVaultName)
SecretsFilter: $(SecretName)
RunAsPreJob: true
- task: PowerShell@2
displayName: "Create AKS Cluster"
inputs:
targetType: filePath
filePath: "./Pipelines/CreateLoadTestInfrastructure.ps1"
arguments: "-tenant $(Tenant) -defaultNamespace $(DefaultNamespace) -resourceGroup $(ResourceGroup) -aksClusterName $(AksClusterName) -spnClientId $(ServicePrincipalId) -spnClientSecret $(AKSSPNClientSecret) -aksRegion $(AksRegion) -nodeVmSize $(NodeVmSize)"

Просмотреть файл

@ -0,0 +1,105 @@
parameters:
- name: IsMultiRegionEnabled
type: boolean
default: false
- name: IsClusterRequired
type: boolean
default: false
- name: JMeterFolderPath
type: string
default: JMeterFiles
- name: JMeterFileName
type: string
default: SampleTestPlan.jmx
- name: Threads
type: number
default: 10
- name: Duration
type: number
default: 300
- name: Loops
type: number
default: 3
- name: RampUpTime
type: number
default: 10
variables:
# Tenant: ""
# Namespace: ""
# ServiceConnection: ""
# KeyVaultName: ""
# SecretNames: "AKSSPNClientSecret, PerfTestClientSecret"
# AKSResourceGroup: ""
# AKSSPNClientId: ""
# AKSRegion1: ""
# AKSClusterNameRegion1: ""
# AKSRegion2: ""
# AKSClusterNameRegion2: ""
# PerfTestResourceId: ""
# PerfTestClientId: ""
CSVFileNames: "users.csv"
pool:
vmImage: "windows-latest"
jobs:
- job: Region_1
displayName: Performance Agent Instance 1
steps:
- template: test-execution-steps.yml
parameters:
RegionNumber: 1
IsClusterRequired: ${{ parameters.IsClusterRequired }}
JMeterFolderPath: ${{ parameters.JMeterFolderPath }}
JMeterFileName: ${{ parameters.JMeterFileName }}
Threads: ${{ parameters.Threads }}
Duration: ${{ parameters.Duration }}
Loops: ${{ parameters.Loops }}
RampUpTime: ${{ parameters.Loops }}
Tenant: $(Tenant)
Namespace: $(Namespace)
ServiceConnection: $(ServiceConnection)
KeyVaultName: $(KeyVaultName)
SecretNames: $(SecretNames)
AKSRegion: $(AKSRegion1)
AKSResourceGroup: $(AKSResourceGroup)
AKSClusterName: $(AKSClusterNameRegion1)
AKSSPNClientId: $(AKSSPNClientId)
PerfTestResourceId: $(PerfTestResourceId)
PerfTestClientId: $(PerfTestClientId)
CSVFileNames: $(CSVFileNames)
- ${{ if eq(parameters.IsMultiRegionEnabled, true) }}:
- job: Region_2
displayName: Performance Agent Instance 2
steps:
- template: test-execution-steps.yml
parameters:
RegionNumber: 2
IsClusterRequired: ${{ parameters.IsClusterRequired }}
JMeterFolderPath: ${{ parameters.JMeterFolderPath }}
JMeterFileName: ${{ parameters.JMeterFileName }}
Threads: ${{ parameters.Threads }}
Duration: ${{ parameters.Duration }}
Loops: ${{ parameters.Loops }}
RampUpTime: ${{ parameters.Loops }}
Tenant: $(Tenant)
Namespace: $(Namespace)
ServiceConnection: $(ServiceConnection)
KeyVaultName: $(KeyVaultName)
SecretNames: $(SecretNames)
AKSRegion: $(AKSRegion2)
AKSResourceGroup: $(AKSResourceGroup)
AKSClusterName: $(AKSClusterNameRegion2)
AKSSPNClientId: $(AKSSPNClientId)
PerfTestResourceId: $(PerfTestResourceId)
PerfTestClientId: $(PerfTestClientId)
CSVFileNames: $(CSVFileNames)

Просмотреть файл

@ -0,0 +1,122 @@
parameters:
- name: IsMultiRegionEnabled
type: boolean
default: false
- name: RegionNumber
type: number
- name: IsClusterRequired
type: boolean
default: false
- name: JMeterFolderPath
type: string
default: JMeterFiles
- name: JMeterFileName
type: string
default: SampleTestPlan.jmx
- name: Threads
type: number
default: 10
- name: Duration
type: number
default: 300
- name: Loops
type: number
default: 3
- name: RampUpTime
type: number
default: 10
- name: Tenant
type: string
- name: Namespace
type: string
- name: ServiceConnection
type: string
- name: KeyVaultName
type: string
- name: SecretNames
type: string
- name: AKSRegion
type: string
- name: AKSResourceGroup
type: string
- name: AKSClusterName
type: string
- name: AKSSPNClientId
type: string
- name: PerfTestResourceId
type: string
- name: PerfTestClientId
type: string
- name: CSVFileNames
type: string
steps:
- checkout: self
- task: AzureKeyVault@1
displayName: Get Keyvault Secrets
inputs:
azureSubscription: ${{ parameters.ServiceConnection }}
KeyVaultName: ${{ parameters.KeyVaultName }}
SecretsFilter: ${{ parameters.SecretNames }}
RunAsPreJob: true
- task: PowerShell@2
displayName: Create AKS Cluster
inputs:
targetType: filePath
filePath: ./Pipelines/CreateLoadTestInfrastructure.ps1
arguments: "-tenant ${{ parameters.Tenant }} -defaultNamespace ${{ parameters.Namespace }} -resourceGroup ${{ parameters.AKSResourceGroup }} -aksClusterName ${{ parameters.AKSClusterName }} -spnClientId ${{ parameters.AKSSPNClientId }} -spnClientSecret $(AKSSPNClientSecret) -aksRegion ${{ parameters.AKSRegion }}"
condition: "and(succeeded(), eq('${{ parameters.IsClusterRequired }}', true))"
- task: PowerShell@2
displayName: Update JMX File Parameters
inputs:
targetType: inline
script: "# Change jmeter parameters\n\nsed -i 's/RESOURCE_ID/$(PerfTestResourceId)/g' ${{ parameters.JMeterFolderPath }}/${{ parameters.JMeterFileName }}\n\nsed -i 's/CLIENT_ID/${{ parameters.PerfTestClientId }}/g' ${{ parameters.JMeterFolderPath }}/${{ parameters.JMeterFileName }}\n\nsed -i 's/CLIENT_SECRET/$(PerfTestClientSecret)/g' ${{ parameters.JMeterFolderPath }}/${{ parameters.JMeterFileName }}\n\nsed -i 's/RAMP_TIME/${{ parameters.RampUpTime }}/g' ${{ parameters.JMeterFolderPath }}/${{ parameters.JMeterFileName }}\n \nsed -i 's/DURATION/${{ parameters.Duration }}/g' ${{ parameters.JMeterFolderPath }}/${{ parameters.JMeterFileName }}\n\nsed -i 's/LOOPS/${{ parameters.Loops }}/g' ${{ parameters.JMeterFolderPath }}/${{ parameters.JMeterFileName }}\n\nsed -i 's/THREADS/${{ parameters.Threads }}/g' ${{ parameters.JMeterFolderPath }}/${{ parameters.JMeterFileName }}\n"
condition: "succeeded()"
- task: PowerShell@2
displayName: Run Performance in region ${{ parameters.RegionNumber }}
inputs:
targetType: filePath
filePath: ./Pipelines/ExecuteInAKS.ps1
arguments: '-AKSClusterName ${{ parameters.AKSClusterName }} -ResourceGroup ${{ parameters.AKSResourceGroup }} -SPNClientId ${{ parameters.AKSSPNClientId }} -SPNClientSecret $(AKSSPNClientSecret) -Tenant ${{ parameters.Tenant }} -Namespace ${{ parameters.Namespace }} -JMeterFolderPath ${{ parameters.JMeterFolderPath }} -JMeterFileName ${{ parameters.JMeterFileName }} -CSVFileNames "${{ parameters.CSVFileNames }}"'
pwsh: true
condition: "succeeded()"
- task: PublishPipelineArtifact@1
displayName: Publish Pipeline Artifact
continueOnError: True
inputs:
path: ${{ parameters.JMeterFolderPath }}/${{ parameters.AKSClusterName }}
artifactName: Results-region${{ parameters.RegionNumber }}-${{ parameters.Threads }}-${{ parameters.Duration }}
condition: "succeeded()"
- task: PowerShell@2
displayName: Delete AKS Cluster
inputs:
targetType: filePath
filePath: ./Pipelines/DeleteLoadTestInfrastructure.ps1
arguments: "-tenant ${{ parameters.Tenant }} -aksClusterName ${{ parameters.AKSClusterName }} -resourceGroup ${{ parameters.AKSResourceGroup }} -spnClientId ${{ parameters.AKSSPNClientId }} -spnClientSecret $(AKSSPNClientSecret)"
condition: "and(succeeded(), eq('${{ parameters.IsClusterRequired }}', true))"

186
README.md
Просмотреть файл

@ -1,63 +1,157 @@
# Project
Automated Performance Pipeline using Apache JMeter and AKS
As the Azure DevOps cloud-based load testing by Microsoft has been deprecated, we evaluated the options and finalized on using Apache JMeter with Azure Kubernetes Service (AKS) in a distributed architecture to carry out an intensive load test by simulating hundreds and thousands of simultaneous users.
![image](https://user-images.githubusercontent.com/81369583/114204849-499b3b00-9977-11eb-811d-2c2ff7248f11.png)
![image](https://user-images.githubusercontent.com/81369583/114204849-499b3b00-9977-11eb-811d-2c2ff7248f11.png)
Currently we have also implemented an automated pipeline for running the performance test using Apache JMeter and AKS, which is also extended to simulate parallel load from multiple regions to reproduce a production scenario.
# Prerequisite for onboarding to the automated pipeline:
<br>
We also have another pipeline to set up the load test infrastructure along with scripts to run test locally. This is recommended when more control and precision is required.
## JMeter test scripts:
1. create the test suite with the help of how to setup JMeter test plan(https://jmeter.apache.org/usermanual/build-web-test-plan.html).
2. Check in the JMX file and supporting files in a repository
## AKS setup
1. Create AKS cluster with the help of how to create a AKS cluster(https://docs.microsoft.com/en-us/azure/aks/kubernetes-walkthrough-portal)
2. Provide access to a Service Principal Name which would be used to run the JMX file in the cluster.
# Contents
## Steps to onboarding for the pipeline:
1. Fork the YAML pipeline from the repository: JMeterAKSLoadTest(https://github.com/microsoft/JMeterAKSLoadTest.git)
2. Folder structure looks like below:
![image](https://user-images.githubusercontent.com/81369583/114205274-bf9fa200-9977-11eb-9588-3185151bb711.png)
3. Inside the JMeterFiles folder add the JMX and supporting files there
![image](https://user-images.githubusercontent.com/81369583/114205337-d34b0880-9977-11eb-9b79-d728989469b0.png)
4. Overview on the variable set up:
- JMX file has below variables, which can be used from the variable group or pipeline variables according to the setup:
1. PerfTestResourceId – Resource Id for the API Auth
2. PerfTestClientId – Client Id for the API Auth
3. PerfTestClientSecret – Client secret for the API Auth
4. JmeterFolderPath – JMX File folder path
5. JmeterFileName – JMX File name
- AKS set up related variables:
1. AKSClusterNameRegion1 -Cluster name of the respective region
2. AKSResourceGroupRegion1 – Cluster resource name for the region
3. AKSSPNClientIdRegion1 – client id for the region
4. AKSSPNClientSecretRegion1 – client secret for the region
5. TenantId – tenant id
6. CSVFileNames – list of supported file names for execution like “users.csv,ids.csv”
![image](https://user-images.githubusercontent.com/81369583/114205527-0097b680-9978-11eb-90a4-45bd8c0a7326.png)
5. Set the mentioned pipeline variables as shown:
![image](https://user-images.githubusercontent.com/81369583/114205558-08575b00-9978-11eb-8b1c-999b00f8e924.png)
- [Prerequisites](#prerequisites-for-onboarding-to-the-automated-pipeline)
- [Test Execution Pipeline](#test-execution-pipeline)
- [Load Test Infrastructure Pipeline](#load-test-infrastructure-pipeline)
- [More about AKS](#more-about-aks)
6. Set the Variable group linked from Key vault.
## Prerequisites for onboarding to the automated pipeline
7. The results of the execution is published as artifact and it can be downloaded. The index.html file holds the report of the run.
Prerequisite script creates Service Connection, App Id, Service Principal and KeyVault. KeyVault has certificate and client secret.
Steps to execute Prerequisite script:
1. Set working directory to Scripts folder where Prerequisite.ps1 resides.
2. Run below command.
```Powershell
.\Prerequisite -subscriptionId {Azure Subscription Id} -resourceGroupName {Resource Group Name} -keyVaultName {KeyVault Name} -location {Location} -certName {Certificate Name} -servicePrincipalName {Service Principal Name} -tenantId {Microsoft Tenand Id} -serviceConnectionName {Service Connection Name} -organizationName 'https://microsoftit.visualstudio.com' -projectName 'OneITVSO' -ServicePrincipalSecret {Service Principal Sercret Name}
```
3. After execution of Prerequisite script, Search Service Principal Name in Azure Active Directory and fetch App Id.
4. Onboard App Id to resource group with Contributor role.
### JMeter test scripts:
1. create the test suite with the help of how to setup JMeter test plan(https://jmeter.apache.org/usermanual/build-web-test-plan.html).
2. Check in the JMX file and supporting files in a repository
## Test Execution Pipeline
The test execution pipeline will help users to run tests with maximum automation. Once prerequisites are setup, users can straight away run their tests.
For users who do not have a AKS cluster, the pipeline parameter `IsClusterRequired` will give an option to create this on the fly (it will get deleted after the test).
This pipeline is recommended for tests less than 1 hour to avoid timeout. For running tests greater than 1 hr an advanced setup maybe required, refer to [Load Test Infrastructure Pipeline](#load-test-infrastructure-pipeline) for more details
### Steps to onboard and execute the pipeline:
1. Fork or create pipeline using the `test-execution-pipeline.yml`
2. Folder structure looks like below
![Folder Structure](./Images/folder-structure.png)
3. Inside the JMeterFiles folder add the JMX and supporting files there
![JMeter Files](./Images/JMeter-files.png)
4. Overview on the test execution pipeline variables which can be added by users before running the pipeline -
- Tenant – Tenant id
- NameSpace - AKS cluster namespace
- ServiceConnection - Azure service connection
- KeyVaultName - Key vault name for fetching the secrets used in the pipeline
- SecretNames - List of secrets which can be fetched from the key vault e.g. "AKSSPNClientSecret, PerfTestClientSecret"
- AKSResourceGroup - Resource group for keeping AKS resources
- AKSRegion1 - Respective region name e.g. westus2
- AKSRegion2 - Respective region name e.g. cus
- AKSClusterNameRegion1 - Cluster name of the respective region
- AKSClusterNameRegion2 - Cluster name of the respective region
- AKSSPNClientId – Service principal id used for connecting to AKS clusters
- AKSSPNClientSecret – Client secret used for connecting to AKS clusters
- PerfTestResourceId – Resource Id for the API Auth
- PerfTestClientId – Client Id for the API Auth
- CSVFileNames – List of supported file names for execution like “users.csv,ids.csv”
![Pipeline variables](./Images/pipeline-variables.png)
5. Overview on the test execution pipeline parameters which can be configured at every run while running the pipeline -
- IsMultiRegionEnabled - Allows user to optionally choose to run their workloads in more than one region
- IsClusterRequired - Allows users to optionally create and tear down the cluster on demand while running the tests
- JMeterFolderPath – JMX File folder path
- JMeterFileName – JMX File name
- Threads - Number of threads
- Duration - Duration of the test
- Loops - Number of loops
- RampUpTime -Rampup time used to generate load from JMX file
![Pipeline parameters](./Images/pipeline-parameters.png)
6. The results of the execution is published as artifact and it can be downloaded. The index.html file holds the report of the run.
### Advantages:
1. With minimal cost you can simulate parallel load from different regions to replicate the production scenario.
2. As all the Loops, Threads and Ramp up time variables are configured through pipeline variables you can run the test suite with minimal changes
3. Once the setup is complete no dependency on any specific machine or user credential, therefore it could be run more frequently to understand the application performance.
## Load Test Infrastructure Pipeline
It creates AKS cluster in the desired subscription and resource group. It also creates a default namespace. After creation of the resources required for load test, users can run test multiple times. This setup allows users to run their tests for more than an hour and can be useful in scenarios where more control of the test setup is required. After completion of testing cycle it is recommended to clean the test resources.
### Steps for running load infrastructure pipeline
1. For first time setup in your subscription/resource group, ensure to run the pre-requsites script which will setup the Service Principal, Service Connection, Keyvault etc. Check the [Prerequisites](#prerequisites-for-onboarding-to-the-automated-pipeline) section for more details.
2. Fork or create pipeline using the `load-infrastructure-pipeline.yml`
3. Run the pipeline using the following variables -
- DefaultNamespace
- AksClusterName
- ResourceGroup
- ServicePrincipalId
- AksRegion
- NodeVmSize
- ServiceConnection
- KeyVaultName
- SecretNames
- Tenant
![Pipeline variables infra](./Images/pipeline-variables-infra.png)
### Pre-requisites for running tests on user machine via scripts
- Ensure you have the Azure CLI version 2.9.0 or later
- Ensure you have the aks-preview CLI extension v0.4.55 or higher installed
- Ensure you have installed kubectl v1.18.3+
- Get sufficient permissions on the AKS cluster
### Steps to run tests locally on user's machine
- Run Following
```Powershell
cd .\Scripts\
.\Run-Test.ps1 -aksClusterName {cluster-name} -resourceGroup {rg-name} -testPath {Full Path to Test File} -agentCount {agent-count}
```
- It creates JMeter master and slave pods on AKS
- Copies Test plan to the master JMeter Pod
- Executes the test plan, after test execution JMeter master and slave pods are deleted
![Pipeline variables infra](./Images/run-test-locally.png)
### Collecting Test Results
- At the end of test, path to test results will be displayed in script
- index.html file will give a brief summary of the test execution
- Test report and jmeter server logs can be collected from the report folder
## More about AKS
- Create AKS cluster with the help of how to create a AKS cluster(https://docs.microsoft.com/en-us/azure/aks/kubernetes-walkthrough-portal)
- Alternately, it can be created using the [Load Test Infrastrucure Pipeline](#load-test-infrastrucure-pipeline).
- Provide access to a Service Principal Name which would be used to run the JMX file in the cluster.
## Advantages:
1. With minimal cost you can simulate parallel load from different regions to replicate the production scenario.
2. As all the Loops, Threads and Ramp up time variables are configured through pipeline variables you can run the test suite with minimal changes
3. Once the setup is complete no dependency on any specific machine or user credential, therefore it could be run more frequently to understand the application performance.
## Contributing
This project welcomes contributions and suggestions. Most contributions require you to agree to a
This project welcomes contributions and suggestions. Most contributions require you to agree to a
Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com.
@ -71,8 +165,8 @@ contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additio
## Trademarks
This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft
trademarks or logos is subject to and must follow
This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft
trademarks or logos is subject to and must follow
[Microsoft's Trademark & Brand Guidelines](https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks/usage/general).
Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship.
Any use of third-party trademarks or logos are subject to those third-party's policies.

Просмотреть файл

@ -0,0 +1,43 @@
param(
[Parameter(Mandatory = $True)]
[string]$namespace,
[Parameter(Mandatory = $False)]
[int]$agentCount = 1
)
Set-StrictMode -Version latest
$ErrorActionPreference = "Stop"
function log([string] $message, [string] $color) {
Write-Host "$(get-date) $message" -ForegroundColor $color
Write-Host " "
}
try {
$PathToManifestFolder = Join-Path -Path (Split-Path $(Get-Location) -Parent) -ChildPath "jmeter-kubernetes-setup"
#################### Creating JMeter Master and Slave pods ################################################
log "############## Creating JMeter Slave ##############" "DarkYellow"
kubectl -n $namespace apply -f $(Join-Path -Path $PathToManifestFolder -ChildPath "jmeter_slaves_deploy.yaml")
kubectl -n $namespace apply -f $(Join-Path -Path $PathToManifestFolder -ChildPath "jmeter_slaves_svc.yaml")
log "############## JMeter Slave deployment completed ##############" "DarkGreen"
log "############## Creating JMeter Master ##############" "DarkYellow"
kubectl -n $namespace apply -f $(Join-Path -Path $PathToManifestFolder -ChildPath "jmeter_master_configmap.yaml")
kubectl -n $namespace apply -f $(Join-Path -Path $PathToManifestFolder -ChildPath "jmeter_master_deploy.yaml")
log "############## JMeter Master deployment completed ##############" "DarkGreen"
if ($agentCount -gt 1) {
log "############## Creating $agentCount replicas of JMeter Slave ##############" "DarkYellow"
kubectl scale -n $namespace --replicas=$agentCount deployment/jmeter-slaves
}
log "############## Total number of Slave pods running the tests $agentCount ##############" "DarkYellow"
kubectl -n $namespace rollout status deployment jmeter-master
kubectl -n $namespace rollout status deployment jmeter-slaves
}
catch {
Write-Error "An error occurred while creating JMeter cluster"
Write-Host $_.ScriptStackTrace
}

Просмотреть файл

@ -0,0 +1,26 @@
param(
[Parameter(Mandatory = $True)]
[string]$namespace
)
Set-StrictMode -Version latest
$ErrorActionPreference = "Stop"
function log([string] $message, [string] $color) {
Write-Host "$(get-date) $message" -ForegroundColor $color
Write-Host " "
}
try {
$PathToManifestFolder = Join-Path -Path (Split-Path $(Get-Location) -Parent) -ChildPath "jmeter-kubernetes-setup"
log "############## Removing JMeter Master and Slave pods ##############" "DarkYellow"
kubectl -n $namespace delete -f $(Join-Path -Path $PathToManifestFolder -ChildPath "jmeter_master_deploy.yaml") --wait=true
kubectl -n $namespace delete -f $(Join-Path -Path $PathToManifestFolder -ChildPath "jmeter_slaves_deploy.yaml") --wait=true
kubectl -n $namespace wait --for=delete pods --selector=jmeter_mode=master --timeout=60s
kubectl -n $namespace wait --for=delete pods --selector=jmeter_mode=slave --timeout=60s
}
catch {
Write-Error "An error occurred while deleting JMeter cluster"
Write-Host $_.ScriptStackTrace
}

Просмотреть файл

@ -0,0 +1,38 @@
{
"issuerParameters": {
"certificateTransparency": null,
"name": "Self"
},
"keyProperties": {
"curve": null,
"exportable": true,
"keySize": 2048,
"keyType": "RSA",
"reuseKey": true
},
"lifetimeActions": [
{
"action": {
"actionType": "AutoRenew"
},
"trigger": {
"daysBeforeExpiry": 90
}
}
],
"secretProperties": {
"contentType": "application/x-pem-file"
},
"x509CertificateProperties": {
"keyUsage": [
"cRLSign",
"dataEncipherment",
"digitalSignature",
"keyEncipherment",
"keyAgreement",
"keyCertSign"
],
"subject": "CN=CLIGetDefaultPolicy",
"validityInMonths": 12
}
}

112
Scripts/Prerequisite.ps1 Normal file
Просмотреть файл

@ -0,0 +1,112 @@
param(
[Parameter(Mandatory = $true)]
[string]$subscriptionId,
[Parameter(Mandatory = $true)]
[string]$resourceGroupName,
[Parameter(Mandatory = $true)]
[string]$keyVaultName,
[Parameter(Mandatory = $true)]
[string]$location,
[Parameter(Mandatory = $true)]
[string]$certName,
[Parameter(Mandatory = $true)]
[string]$ServicePrincipalSecret,
[Parameter(Mandatory = $true)]
[string]$servicePrincipalName,
[Parameter(Mandatory = $true)]
[string]$tenantId,
[Parameter(Mandatory = $true)]
[string]$serviceConnectionName,
[Parameter(Mandatory = $true)]
[string]$organizationName,
[Parameter(Mandatory = $true)]
[string]$projectName
)
try {
$pemCertFile = 'cert.pem'
$isLoggedIn = az account show
if ($null -eq $isLoggedIn) {
Write-Host "logging In..." -ForegroundColor White
az login
}
Write-Host "Initiating pre-requisites validation..." -ForegroundColor White
$subscriptionName = az account list --query "[?id=='$subscriptionId'].name" -o tsv
if ($null -eq $subscriptionName) {
Write-Warning "You don't have access to subcription: $subscriptionId ."
exit
}
else {
Write-Host "Switching to subscription: $subscriptionId" -ForegroundColor White
az account set -s $subscriptionId
}
$rgAccess = az group show --name $resourceGroupName --query "name" -o tsv
if ($null -eq $rgAccess) {
Write-Warning "You don't have access to resource group: $resourceGroupName."
exit
}
Write-Host "Checking the required extensions ..." -ForegroundColor White
$extension = 'azure-devops'
$extensionName = az extension show --name $extension --query "[name]" -o tsv
if ($null -eq $extensionName) {
Write-Host "Installing $extension via azure CLI ..." -ForegroundColor White
az extension add --name $extension --yes
}
Write-Host "Pre-requisites check has completed." -ForegroundColor Green
Write-Host "Executing pre-requisite setup script." -ForegroundColor White
$isKVAvailable = $(az keyvault list --query "[?name=='$keyVaultName'] | length(@)" -g $resourceGroupName)
if($isKVAvailable -eq 1) {
Write-Host "keyvault: $keyVaultName already available ..." -ForegroundColor White
}
else {
Write-Host "Creating keyvault: $keyVaultName ..." -ForegroundColor White
az keyvault create --name $keyVaultName --resource-group $resourceGroupName --location $location
}
Write-Host "Creating selfsigned certificate in keyvault ..." -ForegroundColor White
az keyvault certificate create --vault-name $keyVaultName -n $certName --policy `@PEMCertCreationPolicy.json
az keyvault secret download --vault-name $keyVaultName -n $certName -f $pemCertFile
#Trim Cert file and remove extra lines
$directoryPath = Get-Location
$filePath = "$($directoryPath)\$($pemCertFile)"
$fileContent = [System.IO.File]::OpenText($filePath)
$text = ($fileContent.readtoend()).trim("`r`n")
$fileContent.close()
$stream = [System.IO.StreamWriter]$filePath
$stream.write($text)
$stream.close()
Write-Host "Creating App registration ..." -ForegroundColor White
az ad app create --display-name $servicePrincipalName
$appId = az ad app list --display-name $servicePrincipalName --query [0].appId
Write-Host "Creating Service Principal ..." -ForegroundColor White
az ad sp create --id $appId
Write-Host "Importing certificate from keyvault to ServicePrincipal ..." -ForegroundColor White
az ad sp credential reset -n $servicePrincipalName --keyvault $keyVaultName --cert $certName --append
Write-Host "Adding ServicePrincipal client secret to keyvault: $keyVaultName ..." -ForegroundColor White
$spnResult = az ad sp credential reset --name $servicePrincipalName
$spnDetail = $spnResult | ConvertFrom-Json
az keyvault secret set --vault-name $keyVaultName --name $ServicePrincipalSecret --value $spnDetail.password
$spId = az ad sp list --display-name $servicePrincipalName --query [0].appId
Write-Host "Adding ServicePrincipal to keyvault access policies ..." -ForegroundColor White
$objectId = az ad sp show --id $spId --query 'objectId'
az keyvault set-policy --name $keyVaultName --object-id $objectId --secret-permissions get list --key-permissions get list --certificate-permissions get list
Write-Host "Creating ServiceConnection ..." -ForegroundColor White
az devops service-endpoint azurerm create --azure-rm-service-principal-id $spId --azure-rm-subscription-id $subscriptionId --azure-rm-subscription-name $subscriptionName --azure-rm-tenant-id $tenantId --name $serviceConnectionName --detect true --azure-rm-service-principal-certificate-path $pemCertFile --org $organizationName -p $projectName
Remove-Item $pemCertFile
Write-Host "Pre-requisites setup done." -ForegroundColor Green
}
catch {
Write-Error "Script failed due to some issues, please retry again." -ForegroundColor Red
}

96
Scripts/Run-Test.ps1 Normal file
Просмотреть файл

@ -0,0 +1,96 @@
param(
[Parameter(Mandatory = $False)]
[string]$namespace = "",
[Parameter(Mandatory = $True)]
[string]$aksClusterName,
[Parameter(Mandatory = $True)]
[string]$resourceGroup,
# Complete path of test
[Parameter(Mandatory = $True)]
[string]$testPath,
# Relative Path to Script Folder
[Parameter(Mandatory = $False)]
[string]$reportFolder = "Reports",
# Add more than 1 instances
[Parameter(Mandatory = $False)]
[int]$agentCount = 1,
[Parameter(Mandatory = $False)]
[bool]$deleteJMeterCluster = $True,
[Parameter(HelpMessage = "Change this to true if you want to keep the namespace intact. Otherwise the namespace will be deleted after the test")]
[bool]$retainNamespace = $False
)
Set-StrictMode -Version latest
$ErrorActionPreference = "Stop"
function log([string] $message, [string] $color) {
Write-Host "$(get-date) $message" -ForegroundColor $color
Write-Host " "
}
$SlavePods = ''
$folderName = "report_" + (Get-Date).tostring("dd-MM-yyyy-hh-mm-ss")
$root = $PSScriptRoot
try {
log "############## Test Execution Started ##############" "DarkCyan"
log "############## Getting Cluster Credentials ##############" "DarkCyan"
az aks get-credentials --name $aksClusterName --resource-group $resourceGroup --admin
if ($namespace -eq '') {
log "############## Namespace not specified so creating new namespace ##############" "DarkCyan"
$chars = [char[]]"abcdefghijklmnopqrstuvwxyz0123456789"
$randomString = [string](($chars | Get-Random -Count 5) -join "")
$namespace = 'jmeter' + "-" + $randomString
kubectl create namespace $namespace
log "############## Created Namespace - $($namespace) ##############" "DarkCyan"
}
$IsEligibleUser = $(kubectl auth can-i create deployments --namespace $namespace)
if ($IsEligibleUser -ne 'yes') {
Write-Error "############## Cannot Continue Test Execution, get contributor access on cluster ##############"
exit
}
Invoke-Expression "& '$root\Create-Jmeter-Cluster.ps1' -namespace $namespace -agentCount $agentCount"
if ($null -eq $(kubectl -n $namespace get pods --selector=jmeter_mode=master --no-headers=true --output=name) ) {
Write-Error "Master pod does not exist"
exit
}
$MasterPod = $(kubectl -n $namespace get pods --selector=jmeter_mode=master --no-headers=true --output=name).Replace("pod/", "")
$TestName = Split-Path $testPath -Leaf
log "############## Copying test plan $TestName to controller pod ##############" "DarkYellow"
kubectl cp $testPath $namespace/${MasterPod}:/$TestName
$SlavePods = $(kubectl -n $namespace get pods --selector=jmeter_mode=slave --no-headers=true --output=name)
$SlavePod = ""
log "############## Executing test ##############" "DarkYellow"
kubectl -n $namespace exec $MasterPod -- /bin/bash /load_test "$TestName"
log "############## Retrieving dashboard and results to : $($root + '\' + $reportFolder + '\' + $folderName) ##############" "Green"
kubectl cp $namespace/${MasterPod}:/report $reportFolder/$folderName
foreach ($SlavePod in $SlavePods) {
$SlavePod = $SlavePod -replace 'pod/', ''
kubectl cp -n $namespace ${SlavePod}:jmeter-server.log $reportFolder/$folderName/jmeter-server-$SlavePod.log
}
}
catch {
Write-Error "An error occurred while running Load Test"
Write-Host $_.ScriptStackTrace
}
finally {
if ($deleteJMeterCluster) {
Invoke-Expression "& '$root\Delete-Jmeter-Cluster.ps1' -namespace $namespace"
}
if (!($retainNamespace)) {
log "############## Deleting namespace $namespace ##############" "DarkCyan"
kubectl delete namespace $namespace
}
log "############## Test Execution Ends ##############" "DarkCyan"
}