diff --git a/Tasks/QuickPerfTest/CltTasksUtility.ps1 b/Tasks/QuickPerfTest/CltTasksUtility.ps1 new file mode 100644 index 000000000000..8f5dd4175666 --- /dev/null +++ b/Tasks/QuickPerfTest/CltTasksUtility.ps1 @@ -0,0 +1,191 @@ +function InvokeRestMethod($headers, $contentType, $uri , $method= "Get", $body) +{ + $ServicePoint = [System.Net.ServicePointManager]::FindServicePoint($uri) + $result = Invoke-RestMethod -ContentType "application/json" -UserAgent $global:userAgent -TimeoutSec $global:RestTimeout -Uri $uri -Method $method -Headers $headers -Body $body + $ServicePoint.CloseConnectionGroup("") + return $result +} + +function ComposeTestDropJson($name, $duration, $homepage, $vu, $geoLocation) +{ + $tdjson = @" + { + "dropType": "InplaceDrop", + "loadTestDefinition":{ + "loadTestName":"$name", + "runDuration":$duration, + "urls":["$homepage"], + "browserMixs":[ + {"browserName":"Internet Explorer 11.0","browserPercentage":60.0}, + {"browserName":"Chrome 2","browserPercentage":40.0} + ], + "loadPatternName":"Constant", + "maxVusers":$vu, + "loadGenerationGeoLocations":[ + {"Location":"$geoLocation","Percentage":100} + ] + } + } +"@ + + return $tdjson +} + +function CreateTestDrop($headers, $dropJson, $CltAccountUrl) +{ + $uri = [String]::Format("{0}/_apis/clt/testdrops?api-version=1.0", $CltAccountUrl) + $drop = InvokeRestMethod -contentType "application/json" -uri $uri -method Post -headers $headers -body $dropJson + + return $drop +} + +function GetTestDrop($headers, $drop, $CltAccountUrl) +{ + $uri = [String]::Format("{0}/_apis/clt/testdrops/{1}?api-version=1.0", $CltAccountUrl, $drop.id) + $testdrop = InvokeRestMethod -contentType "application/json" -uri $uri -headers $headers + + return $testdrop +} + +function UploadTestDrop($testdrop) +{ + $uri = New-Object System.Uri($testdrop.accessData.dropContainerUrl) + $sas = New-Object Microsoft.WindowsAzure.Storage.Auth.StorageCredentials($testdrop.accessData.sasKey) + $container = New-Object Microsoft.WindowsAzure.Storage.Blob.CloudBlobContainer($uri, $sas) + + return $container +} + +#function GetTestRuns($headers, $CltAccountUrl) +#{ +# $uri = [String]::Format("{0}/_apis/clt/testruns?api-version=1.0", $CltAccountUrl) +# $runs = InvokeRestMethod -contentType "application/json" -uri $uri -headers $headers + +# return $runs +#} + +function GetTestRunUri($testRunId, $headers, $CltAccountUrl) +{ + $uri = [String]::Format("{0}/_apis/clt/testruns/{1}?api-version=1.0", $CltAccountUrl,$testRunId) + $run = InvokeRestMethod -contentType "application/json" -uri $uri -headers $headers + + return $run.WebResultUrl +} + +function RunInProgress($run) +{ + return $run.state -eq "queued" -or $run.state -eq "inProgress" +} + +function MonitorTestRun($headers, $run, $CltAccountUrl) +{ + $uri = [String]::Format("{0}/_apis/clt/testruns/{1}?api-version=1.0", $CltAccountUrl, $run.id) + $prevState = $run.state + $prevSubState = $run.subState + Write-Output ("Load test '{0}' is in state '{1}|{2}'." -f $run.name, $run.state, $run.subState) + + do + { + Start-Sleep -s 15 + $run = InvokeRestMethod -contentType "application/json" -uri $uri -headers $headers + if ($prevState -ne $run.state -or $prevSubState -ne $run.subState) + { + $prevState = $run.state + $prevSubState = $run.subState + Write-Output ("Load test '{0}' is in state '{1}|{2}'." -f $run.name, $run.state, $run.subState) + } + } + while (RunInProgress $run) + + $run = InvokeRestMethod -contentType "application/json" -uri $uri -headers $headers + Write-Output "------------------------------------" + $uri = [String]::Format("{0}/_apis/clt/testruns/{1}/messages?api-version=1.0", $CltAccountUrl, $run.id) + $messages = InvokeRestMethod -contentType "application/json" -uri $uri -headers $headers + + if ($messages) + { + $timeSorted = $messages.value | Sort-Object loggedDate + foreach ($message in $timeSorted) + { + switch ($message.messageType) + { + "info" { Write-Host -NoNewline ("[Message]{0}" -f $message.message) } + "output" { Write-Host -NoNewline ("[Output]{0}" -f $message.message) } + "warning" { Write-Warning $message.message } + "error" { Write-Error $message.message } + "critical" { Write-Error $message.message } + } + } + } + + Write-Output "------------------------------------" +} + +function ComposeTestRunJson($name, $tdid, $machineType) +{ + $trjson = @" + { + "name":"$name", + "description":"Quick perf test from automation task", + "testSettings":{"cleanupCommand":"", "hostProcessPlatform":"x64", "setupCommand":""}, + "superSedeRunSettings":{"loadGeneratorMachinesType":"$machineType"}, + "testDrop":{"id":"$tdid"}, + "runSourceIdentifier":"build/$env:SYSTEM_DEFINITIONID/$env:BUILD_BUILDID" + } +"@ + + return $trjson +} + +function QueueTestRun($headers, $runJson, $CltAccountUrl) +{ + $uri = [String]::Format("{0}/_apis/clt/testruns?api-version=1.0", $CltAccountUrl) + $run = InvokeRestMethod -contentType "application/json" -uri $uri -method Post -headers $headers -body $runJson + + $start = @" + { + "state": "queued" + } +"@ + + $uri = [String]::Format("{0}/_apis/clt/testruns/{1}?api-version=1.0", $CltAccountUrl, $run.id) + InvokeRestMethod -contentType "application/json" -uri $uri -method Patch -headers $headers -body $start + $run = InvokeRestMethod -contentType "application/json" -uri $uri -headers $headers + + return $run +} + +function ComposeAccountUrl($connectedServiceUrl, $headers) +{ + #Load all dependent files for execution + . $PSScriptRoot/VssConnectionHelper.ps1 + $connectedServiceUrl = $connectedServiceUrl.TrimEnd('/') + Write-Host "Getting Clt Endpoint:" + $elsUrl = Get-CltEndpoint $connectedServiceUrl $headers + + return $elsUrl +} + +function ValidateInputs($websiteUrl, $tfsCollectionUrl, $connectedServiceName) +{ + if (![System.Uri]::IsWellFormedUriString($websiteUrl, [System.UriKind]::Absolute)) + { + throw "Website Url is not well formed." + } + + if([string]::IsNullOrWhiteSpace($connectedServiceName) -and $tfsCollectionUrl -notlike "*VISUALSTUDIO.COM*" -and $tfsCollectionUrl -notlike "*TFSALLIN.NET*") + { + throw "VS Team Services Connection is mandatory for non hosted TFS builds " + } +} + +function UploadSummaryMdReport($summaryMdPath) +{ + Write-Verbose "Summary Markdown Path = $summaryMdPath" + + if (($env:SYSTEM_HOSTTYPE -eq "build") -and (Test-Path($summaryMdPath))) + { + Write-Host "##vso[task.addattachment type=Distributedtask.Core.Summary;name=Load test results;]$summaryMdPath" + } +} + diff --git a/Tasks/QuickPerfTest/Invoke-QuickPerfTest.ps1 b/Tasks/QuickPerfTest/Invoke-QuickPerfTest.ps1 index 880b32e84bec..d6d3289fad50 100644 --- a/Tasks/QuickPerfTest/Invoke-QuickPerfTest.ps1 +++ b/Tasks/QuickPerfTest/Invoke-QuickPerfTest.ps1 @@ -6,7 +6,7 @@ param [String] $env:BUILD_BUILDID, - [String] [Parameter(Mandatory = $true)] + [String] [Parameter(Mandatory = $false)] $connectedServiceName, [String] [Parameter(Mandatory = $true)] @@ -23,212 +23,30 @@ param $machineType ) -$userAgent = "QuickPerfTestBuildTask" - -$global:RestTimeout = 60 - function InitializeRestHeaders() { $restHeaders = New-Object -TypeName "System.Collections.Generic.Dictionary[[String], [String]]" - - $alternateCreds = [String]::Concat($Username, ":", $Password) - $basicAuth = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($alternateCreds)) - $restHeaders.Add("Authorization", [String]::Concat("Basic ", $basicAuth)) - - return $restHeaders -} - -function InvokeRestMethod($headers, $contentType, $uri , $method= "Get", $body) -{ - $ServicePoint = [System.Net.ServicePointManager]::FindServicePoint($uri) - $result = Invoke-RestMethod -ContentType "application/json" -UserAgent $userAgent -TimeoutSec $global:RestTimeout -Uri $uri -Method $method -Headers $headers -Body $body - $ServicePoint.CloseConnectionGroup("") - return $result -} - -function ComposeTestDropJson($name, $duration, $homepage, $vu) -{ -$tdjson = @" -{ - "dropType": "InplaceDrop", - "loadTestDefinition":{ - "loadTestName":"$name", - "runDuration":$duration, - "urls":["$homepage"], - "browserMixs":[ - {"browserName":"Internet Explorer 11.0","browserPercentage":60.0}, - {"browserName":"Chrome 2","browserPercentage":40.0} - ], - "loadPatternName":"Constant", - "maxVusers":$vu, - "loadGenerationGeoLocations":[ - {"Location":"$geoLocation","Percentage":100} - ] - } -} -"@ - - return $tdjson -} - -function CreateTestDrop($headers, $dropJson) -{ - $uri = [String]::Format("{0}/_apis/clt/testdrops?api-version=1.0", $CltAccountUrl) - $drop = InvokeRestMethod -contentType "application/json" -uri $uri -method Post -headers $headers -body $dropJson - - return $drop -} - -function GetTestDrop($headers, $drop) -{ - $uri = [String]::Format("{0}/_apis/clt/testdrops/{1}?api-version=1.0", $CltAccountUrl, $drop.id) - $testdrop = InvokeRestMethod -contentType "application/json" -uri $uri -headers $headers - - return $testdrop -} - -function UploadTestDrop($testdrop) -{ - $uri = New-Object System.Uri($testdrop.accessData.dropContainerUrl) - $sas = New-Object Microsoft.WindowsAzure.Storage.Auth.StorageCredentials($testdrop.accessData.sasKey) - $container = New-Object Microsoft.WindowsAzure.Storage.Blob.CloudBlobContainer($uri, $sas) - - return $container -} - -function GetTestRuns($headers) -{ - $uri = [String]::Format("{0}/_apis/clt/testruns?api-version=1.0", $CltAccountUrl) - $runs = InvokeRestMethod -contentType "application/json" -uri $uri -headers $headers - - return $runs -} - -function GetTestRunUri($testRunId, $headers) -{ - $uri = [String]::Format("{0}/_apis/clt/testruns/{1}?api-version=1.0", $CltAccountUrl,$testRunId) - $run = InvokeRestMethod -contentType "application/json" -uri $uri -headers $headers - - return $run.WebResultUrl -} - -function RunInProgress($run) -{ - return $run.state -eq "queued" -or $run.state -eq "inProgress" -} - -function MonitorTestRun($headers, $run) -{ - $uri = [String]::Format("{0}/_apis/clt/testruns/{1}?api-version=1.0", $CltAccountUrl, $run.id) - $prevState = $run.state - $prevSubState = $run.subState - Write-Output ("Load test '{0}' is in state '{1}|{2}'." -f $run.name, $run.state, $run.subState) - - do - { - Start-Sleep -s 5 - $run = InvokeRestMethod -contentType "application/json" -uri $uri -headers $headers - if ($prevState -ne $run.state -or $prevSubState -ne $run.subState) - { - $prevState = $run.state - $prevSubState = $run.subState - Write-Output ("Load test '{0}' is in state '{1}|{2}'." -f $run.name, $run.state, $run.subState) - } - } - while (RunInProgress $run) - - $run = InvokeRestMethod -contentType "application/json" -uri $uri -headers $headers - Write-Output "------------------------------------" - $uri = [String]::Format("{0}/_apis/clt/testruns/{1}/messages?api-version=1.0", $CltAccountUrl, $run.id) - $messages = InvokeRestMethod -contentType "application/json" -uri $uri -headers $headers - - if ($messages) - { - $timeSorted = $messages.value | Sort-Object loggedDate - foreach ($message in $timeSorted) - { - switch ($message.messageType) - { - "info" { Write-Host -NoNewline ("[Message]{0}" -f $message.message) } - "output" { Write-Host -NoNewline ("[Output]{0}" -f $message.message) } - "warning" { Write-Warning $message.message } - "error" { Write-Error $message.message } - "critical" { Write-Error $message.message } - } - } + if([string]::IsNullOrWhiteSpace($connectedServiceName)) + { + $restHeaders.Add("Authorization", [String]::Concat("Bearer ", $env:SYSTEM_ACCESSTOKEN)) + } - - Write-Output "------------------------------------" -} - -function ComposeTestRunJson($name, $tdid) -{ -$trjson = @" -{ - "name":"$name", - "description":"Quick perf test from automation task", - "testSettings":{"cleanupCommand":"", "hostProcessPlatform":"x86", "setupCommand":""}, - "superSedeRunSettings":{"loadGeneratorMachinesType":"$MachineType"}, - "testDrop":{"id":"$tdid"}, - "runSourceIdentifier":"build/$env:SYSTEM_DEFINITIONID/$env:BUILD_BUILDID" -} -"@ - - return $trjson -} - -function QueueTestRun($headers, $runJson) -{ - $uri = [String]::Format("{0}/_apis/clt/testruns?api-version=1.0", $CltAccountUrl) - $run = InvokeRestMethod -contentType "application/json" -uri $uri -method Post -headers $headers -body $runJson - -$start = @" -{ - "state": "queued" -} -"@ - - $uri = [String]::Format("{0}/_apis/clt/testruns/{1}?api-version=1.0", $CltAccountUrl, $run.id) - InvokeRestMethod -contentType "application/json" -uri $uri -method Patch -headers $headers -body $start - $run = InvokeRestMethod -contentType "application/json" -uri $uri -headers $headers - - return $run -} - -function ComposeAccountUrl($vsoUrl) -{ - $elsUrl = $vsoUrl - - if ($vsoUrl -notlike "*VSCLT.VISUALSTUDIO.COM*") - { - if ($vsoUrl -like "*VISUALSTUDIO.COM*") - { - $accountName = $vsoUrl.Split('//')[2].Split('.')[0] - $elsUrl = ("https://{0}.vsclt.visualstudio.com" -f $accountName) - } - } - - return $elsUrl -} - -function ValidateInputs() -{ - if (![System.Uri]::IsWellFormedUriString($websiteUrl, [System.UriKind]::Absolute)) - { - throw "Website Url is not well formed." - } -} - -function UploadSummaryMdReport($summaryMdPath) -{ - Write-Verbose "Summary Markdown Path = $summaryMdPath" - - if (($env:SYSTEM_HOSTTYPE -eq "build") -and (Test-Path($summaryMdPath))) - { - Write-Host "##vso[task.addattachment type=Distributedtask.Core.Summary;name=Load test results;]$summaryMdPath" + else + { + $alternateCreds = [String]::Concat($Username, ":", $Password) + $basicAuth = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($alternateCreds)) + $restHeaders.Add("Authorization", [String]::Concat("Basic ", $basicAuth)) } + return $restHeaders } + # Load all dependent files for execution + . $PSScriptRoot/CltTasksUtility.ps1 + . $PSScriptRoot/VssConnectionHelper.ps1 + +$userAgent = "QuickPerfTestBuildTask" +$global:RestTimeout = 60 + ############################################## PS Script execution starts here ########################################## Write-Output "Starting Quick Perf Test Script" @@ -245,47 +63,53 @@ Write-Output "Load generator machine type = $machineType" Write-Output "Run source identifier = build/$env:SYSTEM_DEFINITIONID/$env:BUILD_BUILDID" #Validate Input -ValidateInputs - -$connectedServiceDetails = Get-ServiceEndpoint -Context $distributedTaskContext -Name $connectedServiceName +ValidateInputs $websiteUrl $env:SYSTEM_TEAMFOUNDATIONCOLLECTIONURI $connectedServiceName +if([string]::IsNullOrWhiteSpace($connectedServiceName)) +{ + $connectedServiceDetails = Get-ServiceEndpoint -Context $distributedTaskContext -Name SystemVssConnection +} +else +{ + $connectedServiceDetails = Get-ServiceEndpoint -Context $distributedTaskContext -Name $connectedServiceName +} $Username = $connectedServiceDetails.Authorization.Parameters.Username Write-Verbose "Username = $Username" -Verbose $Password = $connectedServiceDetails.Authorization.Parameters.Password $VSOAccountUrl = $connectedServiceDetails.Url.AbsoluteUri -$CltAccountUrl = ComposeAccountUrl($VSOAccountUrl).TrimEnd('/') +Write-Output "VSO Account URL is : $VSOAccountUrl" +$headers = InitializeRestHeaders +$CltAccountUrl = ComposeAccountUrl $VSOAccountUrl $headers $TFSAccountUrl = $env:System_TeamFoundationCollectionUri.TrimEnd('/') -Write-Verbose "VSO account Url = $TFSAccountUrl" -Verbose -Write-Verbose "CLT account Url = $CltAccountUrl" -Verbose +Write-Output "VSO account Url = $TFSAccountUrl" -Verbose +Write-Output "CLT account Url = $CltAccountUrl" -Verbose -$headers = InitializeRestHeaders -$dropjson = ComposeTestDropJson $testName $runDuration $websiteUrl $vuLoad -$drop = CreateTestDrop $headers $dropjson + +$dropjson = ComposeTestDropJson $testName $runDuration $websiteUrl $vuLoad $geoLocation + +$drop = CreateTestDrop $headers $dropjson $CltAccountUrl + if ($drop.dropType -eq "InPlaceDrop") { - $runJson = ComposeTestRunJson $testName $drop.id - - $run = QueueTestRun $headers $runJson - MonitorTestRun $headers $run - $webResultsUrl = GetTestRunUri $run.id $headers + $runJson = ComposeTestRunJson $testName $drop.id $MachineType + $run = QueueTestRun $headers $runJson $CltAccountUrl + MonitorTestRun $headers $run $CltAccountUrl + $webResultsUrl = GetTestRunUri $run.id $headers $CltAccountUrl Write-Output ("Run-id for this load test is {0} and its name is '{1}'." -f $run.runNumber, $run.name) Write-Output ("To view run details navigate to {0}" -f $webResultsUrl) Write-Output "To view detailed results navigate to Load Test | Load Test Manager in Visual Studio IDE, and open this run." - $resultsMDFolder = New-Item -ItemType Directory -Force -Path "$env:Temp\LoadTestResultSummary" - + $resultsMDFolder = New-Item -ItemType Directory -Force -Path "$env:Temp\LoadTestResultSummary" $resultFilePattern = ("QuickPerfTestResults_{0}_{1}_*.md" -f $env:AGENT_ID, $env:SYSTEM_DEFINITIONID) - $excludeFilePattern = ("QuickPerfTestResults_{0}_{1}_{2}_*.md" -f $env:AGENT_ID, $env:SYSTEM_DEFINITIONID, $env:BUILD_BUILDID) Remove-Item $resultsMDFolder\$resultFilePattern -Exclude $excludeFilePattern -Force $summaryFile = ("{0}\QuickPerfTestResults_{1}_{2}_{3}_{4}.md" -f $resultsMDFolder, $env:AGENT_ID, $env:SYSTEM_DEFINITIONID, $env:BUILD_BUILDID, $run.id) - $summary = ('[Test Run: {0}]({1}) using {2}.
' -f $run.runNumber, $webResultsUrl ,$run.name) - ('

{0}

' -f $summary) | Out-File $summaryFile -Encoding ascii -Append + ('

{0}

' -f $summary) >> $summaryFile UploadSummaryMdReport $summaryFile } else diff --git a/Tasks/QuickPerfTest/VssConnectionHelper.ps1 b/Tasks/QuickPerfTest/VssConnectionHelper.ps1 new file mode 100644 index 000000000000..1365d4e155c8 --- /dev/null +++ b/Tasks/QuickPerfTest/VssConnectionHelper.ps1 @@ -0,0 +1,41 @@ +function Get-CltEndpoint($connectedServiceUrl, $headers) +{ + # Load all dependent files for execution + . $PSScriptRoot/CltTasksUtility.ps1 + $vsoUrl = $connectedServiceUrl + Write-Host "Fetching the Clt endpoint for $vsoUrl" + $spsLocation = Get-SpsLocation $vsoUrl $headers + $cltLocation = Get-CltLocation $spsLocation $headers + + return $cltLocation + +} + +function Get-SpsLocation($vsoUrl, $headers) +{ + Write-Host "Fetching the SPS endpoint for $vsoUrl" + $spsUniqueIdentifier = "951917AC-A960-4999-8464-E3F0AA25B381" + $spsLocation = Get-ServiceLocation $vsoUrl $headers $spsUniqueIdentifier + return $spsLocation +} + +function Get-CltLocation($spsUrl, $headers) +{ + Write-Host "Fetching the CLT endpoint for $vsoUrl" + $cltUniqueIdentifier = "6C404D78-EF65-4E65-8B6A-DF19D6361EAE" + return Get-ServiceLocation $spsUrl $headers $cltUniqueIdentifier +} + +function Get-ServiceLocation($baseUrl, $headers, $serviceUniqueIdentifier) +{ + # Load all dependent files for execution + . $PSScriptRoot/CltTasksUtility.ps1 + $locationCallUri = [string]::Format("{0}/_apis/servicedefinitions/LocationService2/{1}", $baseUrl, $serviceUniqueIdentifier) + $locationCallJsonResponse = InvokeRestMethod -Uri $locationCallUri -contentType "application/json" -headers $headers -Method Get + if($locationCallJsonResponse) + { + return $locationCallJsonResponse.locationMappings.location|Select -First 1 + } + + return $null +} \ No newline at end of file diff --git a/Tasks/QuickPerfTest/task.json b/Tasks/QuickPerfTest/task.json index 1de0341df99c..be95cd6f0a49 100644 --- a/Tasks/QuickPerfTest/task.json +++ b/Tasks/QuickPerfTest/task.json @@ -13,7 +13,7 @@ "version": { "Major": 1, "Minor": 0, - "Patch": 13 + "Patch": 14 }, "demands": [ "msbuild", @@ -24,9 +24,8 @@ { "name": "connectedServiceName", "type": "connectedService:Generic", - "label": "Registered connection", + "label": "VS Team Services Connection", "defaultValue": "", - "required": true, "helpMarkDown": "Select a previously registered service connection to talk to the cloud-based load test service. Choose 'Manage' to register a new connection." }, {