Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,13 @@ For using any credentials like Azure Service Principal in your workflow, add the
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
```


## Breaking changes

All minor versions under `1.1` (e.g., `1.1.x`) will stop working due to a breaking change.
To ensure continued compatibility, we recommend switching to the `v1` tag, which we will continue to support.
We are also bumping the minor version to `1.2.1`. If you prefer to pin to a specific version, please update your workflows to use `1.2.1`.

## Azure Load Testing Action

This section describes the Azure Load Testing GitHub action. You can use this action by referencing `azure/load-testing@v1` action in your workflow. The action runs on Windows, Linux, and Mac runners.
Expand Down
9 changes: 5 additions & 4 deletions lib/Constants/EnvironmentConstants.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.AzureUSGovernmentCloud = exports.AzurePublicCloud = void 0;
const TaskParameters_1 = require("../models/TaskParameters");
exports.AzurePublicCloud = {
cloudName: "AzureCloud",
armTokenScope: "https://management.core.windows.net",
dataPlaneTokenScope: "https://loadtest.azure-dev.com",
armTokenScope: TaskParameters_1.armPublicTokenScope,
dataPlaneTokenScope: TaskParameters_1.publicTokenScope,
armEndpoint: "https://management.azure.com",
};
exports.AzureUSGovernmentCloud = {
cloudName: "AzureUSGovernment",
armTokenScope: "https://management.usgovcloudapi.net",
dataPlaneTokenScope: "https://cnt-prod.loadtesting.azure.us",
armTokenScope: TaskParameters_1.armUsGovernmentTokenScope,
dataPlaneTokenScope: TaskParameters_1.usGovernmentTokenScope,
};
18 changes: 16 additions & 2 deletions lib/Utils/AzCliUtility.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,27 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.execAz = execAz;
exports.getDPTokens = getDPTokens;
exports.getAccounts = getAccounts;
const child_process_1 = require("child_process");
function getDPTokens(tokenScope) {
return __awaiter(this, void 0, void 0, function* () {
const cmdArguments = ["account", "get-access-token", "--resource"];
cmdArguments.push(tokenScope);
return execAz(cmdArguments);
});
}
function getAccounts(accountType) {
return __awaiter(this, void 0, void 0, function* () {
const cmdArguments = accountType === 'Subscription' ? ["account", "show"] : ["cloud", "show"];
return execAz(cmdArguments);
});
}
function execAz(cmdArguments) {
return __awaiter(this, void 0, void 0, function* () {
const azCmd = process.platform === "win32" ? "az.cmd" : "az";
return new Promise((resolve, reject) => {
(0, child_process_1.execFile)(azCmd, [...cmdArguments, "--out", "json"], { encoding: "utf8", shell: true }, (error, stdout) => {
(0, child_process_1.execFile)(azCmd, [...cmdArguments, "--out", "json"], { encoding: "utf8", shell: process.platform === "win32" }, (error, stdout) => {
if (error) {
return reject(error);
}
Expand Down
16 changes: 11 additions & 5 deletions lib/Utils/FetchUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,13 @@ const methodEnumToString = {
[UtilModels_1.FetchCallType.patch]: "patch"
};
// (note mohit): shift to the enum later.
function httpClientRetries(urlSuffix_1, header_1, method_1) {
return __awaiter(this, arguments, void 0, function* (urlSuffix, header, method, retries = 1, data, isUploadCall = true, log = true) {
function httpClientRetries(urlSuffix, header, method, retries = 1, data, isUploadCall = true, log = true) {
return __awaiter(this, void 0, void 0, function* () {
let httpResponse;
const retrriableCodes = [408, 429, 500, 502, 503, 504]; // 408 - Request Timeout, 429 - Too Many Requests, 500 - Internal Server Error, 502 - Bad Gateway, 503 - Service Unavailable, 504 - Gateway Timeout
let backOffTimeForRetry = 5; // seconds
let correlationId = `gh-actions-${(0, CommonUtils_1.getUniqueId)()}`;
try {
let correlationId = `gh-actions-${(0, CommonUtils_1.getUniqueId)()}`;
header[UtilModels_1.correlationHeader] = correlationId; // even if we put console.debug its printing along with the logs, so lets just go ahead with the differentiation with azdo, so we can search the timeframe for azdo in correlationid and resource filter.
if (method == UtilModels_1.FetchCallType.get) {
httpResponse = yield httpClient.get(urlSuffix, header);
Expand Down Expand Up @@ -89,22 +91,26 @@ function httpClientRetries(urlSuffix_1, header_1, method_1) {
if (httpResponse.message.statusCode != undefined && httpResponse.message.statusCode >= 300) {
CoreUtils.debug(`correlation id : ${correlationId}`);
}
if (httpResponse.message.statusCode != undefined && [408, 429, 502, 503, 504].includes(httpResponse.message.statusCode)) {
if (httpResponse.message.statusCode != undefined && retrriableCodes.includes(httpResponse.message.statusCode)) {
if (method == UtilModels_1.FetchCallType.patch) {
backOffTimeForRetry += 60; // extra 60 seconds for patch, basically this happens when the service didnot handle some of the external service dependencies, and the external can take time to recover.
}
let err = yield (0, CommonUtils_1.getResultObj)(httpResponse);
throw { message: (err && err.error && err.error.message) ? err.error.message : (0, CommonUtils_1.errorCorrection)(httpResponse) }; // throwing as message to catch it as err.message
}
return httpResponse;
}
catch (err) {
if (retries) {
let sleeptime = (5 - retries) * 1000 + Math.floor(Math.random() * 5001);
let sleeptime = backOffTimeForRetry * 1000;
if (log) {
console.log(`Failed to connect to ${urlSuffix} due to ${err.message}, retrying in ${sleeptime / 1000} seconds`);
}
yield (0, CommonUtils_1.sleep)(sleeptime);
return yield httpClientRetries(urlSuffix, header, method, retries - 1, data);
}
else {
console.log(err, "\ncorrelationId:" + correlationId);
throw new Error(`Operation did not succeed after 3 retries. Pipeline failed with error : ${err.message}`);
}
}
Expand Down
6 changes: 2 additions & 4 deletions lib/Utils/TaskParametersUtil.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,7 @@ class TaskParametersUtil {
static setSubscriptionParameters(taskParameters) {
return __awaiter(this, void 0, void 0, function* () {
try {
const cmdArguments = ["account", "show"];
var result = yield AzCliUtility.execAz(cmdArguments);
var result = yield AzCliUtility.getAccounts('Subscription');
taskParameters.subscriptionId = result.id;
taskParameters.subscriptionName = result.name;
}
Expand All @@ -98,8 +97,7 @@ class TaskParametersUtil {
static setEndpointAndScopeParameters(taskParameters) {
return __awaiter(this, void 0, void 0, function* () {
try {
const cmdArguments = ["cloud", "show"];
var result = yield AzCliUtility.execAz(cmdArguments);
var result = yield AzCliUtility.getAccounts('Cloud');
let env = result ? result.name : null;
taskParameters.environment = env !== null && env !== void 0 ? env : EnvironmentConstants.AzurePublicCloud.cloudName;
let endpointUrl = (result && result.endpoints) ? result.endpoints.resourceManager : null;
Expand Down
5 changes: 5 additions & 0 deletions lib/models/TaskParameters.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,7 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.armUsGovernmentTokenScope = exports.armPublicTokenScope = exports.usGovernmentTokenScope = exports.publicTokenScope = void 0;
exports.publicTokenScope = "https://cnt-prod.loadtesting.azure.com";
exports.usGovernmentTokenScope = "https://cnt-prod.loadtesting.azure.us";
exports.armPublicTokenScope = "https://management.core.windows.net";
exports.armUsGovernmentTokenScope = "https://management.usgovcloudapi.net";
4 changes: 1 addition & 3 deletions lib/services/AuthenticatorService.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,7 @@ class AuthenticatorService {
return __awaiter(this, void 0, void 0, function* () {
let tokenScopeDecoded = scope == UtilModels_1.TokenScope.Dataplane ? this.taskParameters.dataPlaneTokenScope : this.taskParameters.armTokenScope;
try {
const cmdArguments = ["account", "get-access-token", "--resource"];
cmdArguments.push(tokenScopeDecoded);
let result = yield AzCliUtility.execAz(cmdArguments);
let result = yield AzCliUtility.getDPTokens(tokenScopeDecoded);
let token = result.accessToken;
scope == UtilModels_1.TokenScope.ControlPlane ? this.controlPlaneToken = token : this.dataPlanetoken = token;
return token;
Expand Down
16 changes: 9 additions & 7 deletions src/Constants/EnvironmentConstants.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
import { ControlPlaneTokenScope, DataPlaneTokenScope, publicTokenScope, usGovernmentTokenScope, armUsGovernmentTokenScope, armPublicTokenScope } from "../models/TaskParameters";

type EnvironmentSettings = {
cloudName: string;
armTokenScope: string;
dataPlaneTokenScope: string;
armTokenScope: ControlPlaneTokenScope;
dataPlaneTokenScope: DataPlaneTokenScope;
armEndpoint?: string;
}

export const AzurePublicCloud: EnvironmentSettings = {
cloudName: "AzureCloud",
armTokenScope: "https://management.core.windows.net",
dataPlaneTokenScope: "https://loadtest.azure-dev.com",
armTokenScope: armPublicTokenScope,
dataPlaneTokenScope: publicTokenScope,
armEndpoint: "https://management.azure.com",
}

export const AzureUSGovernmentCloud: EnvironmentSettings = {
cloudName: "AzureUSGovernment",
armTokenScope: "https://management.usgovcloudapi.net",
dataPlaneTokenScope: "https://cnt-prod.loadtesting.azure.us",
}
armTokenScope: armUsGovernmentTokenScope,
dataPlaneTokenScope: usGovernmentTokenScope,
}
18 changes: 15 additions & 3 deletions src/Utils/AzCliUtility.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,21 @@
import { execFile } from "child_process";
import { AccountType, ControlPlaneTokenScope, DataPlaneTokenScope } from "../models/TaskParameters";

export async function execAz(cmdArguments: string[]): Promise<any> {
export async function getDPTokens(tokenScope: ControlPlaneTokenScope| DataPlaneTokenScope): Promise<any> {
const cmdArguments = ["account", "get-access-token", "--resource"];
cmdArguments.push(tokenScope);
return execAz(cmdArguments);
}

export async function getAccounts(accountType: AccountType): Promise<any> {
const cmdArguments = accountType === 'Subscription' ? ["account", "show"] : ["cloud", "show"];
return execAz(cmdArguments);
}

async function execAz(cmdArguments: string[]): Promise<any> {
const azCmd = process.platform === "win32" ? "az.cmd" : "az";
return new Promise<any>((resolve, reject) => {
execFile(azCmd, [...cmdArguments, "--out", "json"], { encoding: "utf8", shell : true }, (error:any, stdout:any) => {
execFile(azCmd, [...cmdArguments, "--out", "json"], { encoding: "utf8", shell : process.platform === "win32" }, (error:any, stdout:any) => {
if (error) {
return reject(error);
}
Expand All @@ -17,4 +29,4 @@ export async function execAz(cmdArguments: string[]): Promise<any> {
}
});
});
}
}
14 changes: 10 additions & 4 deletions src/Utils/FetchUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@ const methodEnumToString : { [key in FetchCallType] : string } = {
// (note mohit): shift to the enum later.
export async function httpClientRetries(urlSuffix : string, header : IHeaders, method : FetchCallType , retries : number = 1, data : string , isUploadCall : boolean = true, log: boolean = true) : Promise<IHttpClientResponse>{
let httpResponse : IHttpClientResponse;
const retrriableCodes = [408,429,500,502,503,504]; // 408 - Request Timeout, 429 - Too Many Requests, 500 - Internal Server Error, 502 - Bad Gateway, 503 - Service Unavailable, 504 - Gateway Timeout
let backOffTimeForRetry = 5; // seconds
let correlationId = `gh-actions-${getUniqueId()}`;
try {
let correlationId = `gh-actions-${getUniqueId()}`;
header[correlationHeader] = correlationId; // even if we put console.debug its printing along with the logs, so lets just go ahead with the differentiation with azdo, so we can search the timeframe for azdo in correlationid and resource filter.
if(method == FetchCallType.get){
httpResponse = await httpClient.get(urlSuffix, header);
Expand All @@ -45,23 +47,27 @@ export async function httpClientRetries(urlSuffix : string, header : IHeaders, m
if(httpResponse.message.statusCode!= undefined && httpResponse.message.statusCode >= 300){
CoreUtils.debug(`correlation id : ${correlationId}`);
}
if(httpResponse.message.statusCode!=undefined && [408,429,502,503,504].includes(httpResponse.message.statusCode)){
if(httpResponse.message.statusCode!=undefined && retrriableCodes.includes(httpResponse.message.statusCode)){
if(method == FetchCallType.patch){
backOffTimeForRetry += 60; // extra 60 seconds for patch, basically this happens when the service didnot handle some of the external service dependencies, and the external can take time to recover.
}
let err = await getResultObj(httpResponse);
throw {message : (err && err.error && err.error.message) ? err.error.message : errorCorrection(httpResponse)}; // throwing as message to catch it as err.message
}
return httpResponse;
}
catch(err:any){
if(retries){
let sleeptime = (5-retries)*1000 + Math.floor(Math.random() * 5001);
let sleeptime = backOffTimeForRetry * 1000;
if (log) {
console.log(`Failed to connect to ${urlSuffix} due to ${err.message}, retrying in ${sleeptime/1000} seconds`);
}
await sleep(sleeptime);
return await httpClientRetries(urlSuffix,header,method,retries-1,data);
}
else{
console.log(err, "\ncorrelationId:" + correlationId);
throw new Error(`Operation did not succeed after 3 retries. Pipeline failed with error : ${err.message}`);
}
}
}
}
8 changes: 3 additions & 5 deletions src/Utils/TaskParametersUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,7 @@ export class TaskParametersUtil {

private static async setSubscriptionParameters(taskParameters: TaskParameters) {
try {
const cmdArguments = ["account", "show"];
var result: any = await AzCliUtility.execAz(cmdArguments);
var result: any = await AzCliUtility.getAccounts('Subscription');

taskParameters.subscriptionId = result.id;
taskParameters.subscriptionName = result.name;
Expand All @@ -60,8 +59,7 @@ export class TaskParametersUtil {
private static async setEndpointAndScopeParameters(taskParameters: TaskParameters) {
try
{
const cmdArguments = ["cloud", "show"];
var result: any = await AzCliUtility.execAz(cmdArguments);
var result: any = await AzCliUtility.getAccounts('Cloud');
let env = result ? result.name : null;
taskParameters.environment = env ?? EnvironmentConstants.AzurePublicCloud.cloudName;

Expand All @@ -80,4 +78,4 @@ export class TaskParametersUtil {
throw new Error(message);
}
}
}
}
15 changes: 12 additions & 3 deletions src/models/TaskParameters.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
export const publicTokenScope = "https://cnt-prod.loadtesting.azure.com";
export const usGovernmentTokenScope = "https://cnt-prod.loadtesting.azure.us";
export const armPublicTokenScope = "https://management.core.windows.net";
export const armUsGovernmentTokenScope = "https://management.usgovcloudapi.net";
export type ControlPlaneTokenScope = typeof armPublicTokenScope | typeof armUsGovernmentTokenScope;
export type DataPlaneTokenScope = typeof publicTokenScope | typeof usGovernmentTokenScope;

export type AccountType = 'Subscription' | 'Cloud'; // cloud is what the service principal logged into, should be only 1.

export interface TaskParameters {
subscriptionId: string;
subscriptionName: string;
environment: string;
armTokenScope: string;
dataPlaneTokenScope: string;
armTokenScope: ControlPlaneTokenScope;
dataPlaneTokenScope: DataPlaneTokenScope;
resourceId: string;
armEndpoint?: string;
}
}
6 changes: 2 additions & 4 deletions src/services/AuthenticatorService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,7 @@ export class AuthenticatorService {
{
let tokenScopeDecoded = scope == TokenScope.Dataplane ? this.taskParameters.dataPlaneTokenScope : this.taskParameters.armTokenScope;
try {
const cmdArguments = ["account", "get-access-token", "--resource"];
cmdArguments.push(tokenScopeDecoded);
let result: any = await AzCliUtility.execAz(cmdArguments);
let result: any = await AzCliUtility.getDPTokens(tokenScopeDecoded);
let token = result.accessToken;
scope == TokenScope.ControlPlane ? this.controlPlaneToken = token : this.dataPlanetoken = token;
return token;
Expand All @@ -66,4 +64,4 @@ export class AuthenticatorService {
console.log("Error in getting the token");
}
}
}
}
2 changes: 1 addition & 1 deletion test/Utils/TestSupport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export class TestSupport {
}

public static setupMockForPostProcess(isTestRunCompleted: boolean = false) {
let stub = sinon.stub(AzCliUtility, "execAz");
let stub = sinon.stub(AzCliUtility, "getAccounts");
let cloudShowResult = {
name: EnvironmentConstants.AzurePublicCloud.cloudName,
endpoints: {
Expand Down
4 changes: 2 additions & 2 deletions test/apiService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ describe('api service tests', () => {
await expect(apiService.deleteFileAPI("samplefile.jmx")).rejects.toThrow();
expect(authenticatorServiceMock.getDataPlaneHeaderCalled()).toBe(true);
expect(authenticatorServiceMock.getARMTokenHeaderCalled()).toBe(false);
});
}, 40000);

it("createTestAPI returns resp on 201", async () => {
let testId = "testid1";
Expand Down Expand Up @@ -306,4 +306,4 @@ describe('api service tests', () => {
expect(authenticatorServiceMock.getDataPlaneHeaderCalled()).toBe(true);
expect(authenticatorServiceMock.getARMTokenHeaderCalled()).toBe(false);
});
})
})
Loading
Loading