@depup/artillery 2.0.30-depup.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +63 -0
- package/bin/run +29 -0
- package/bin/run.cmd +3 -0
- package/changes.json +138 -0
- package/console-reporter.js +1 -0
- package/lib/artillery-global.js +33 -0
- package/lib/cli/banner.js +8 -0
- package/lib/cli/common-flags.js +80 -0
- package/lib/cli/hooks/version.js +20 -0
- package/lib/cmds/dino.js +109 -0
- package/lib/cmds/quick.js +122 -0
- package/lib/cmds/report.js +34 -0
- package/lib/cmds/run-aci.js +91 -0
- package/lib/cmds/run-fargate.js +192 -0
- package/lib/cmds/run-lambda.js +96 -0
- package/lib/cmds/run.js +671 -0
- package/lib/console-capture.js +92 -0
- package/lib/console-reporter.js +438 -0
- package/lib/create-bom/built-in-plugins.js +12 -0
- package/lib/create-bom/create-bom.js +301 -0
- package/lib/dispatcher.js +9 -0
- package/lib/dist.js +222 -0
- package/lib/index.js +5 -0
- package/lib/launch-platform.js +439 -0
- package/lib/load-plugins.js +113 -0
- package/lib/platform/aws/aws-cloudwatch.js +106 -0
- package/lib/platform/aws/aws-create-sqs-queue.js +58 -0
- package/lib/platform/aws/aws-ensure-s3-bucket-exists.js +78 -0
- package/lib/platform/aws/aws-get-account-id.js +26 -0
- package/lib/platform/aws/aws-get-bucket-region.js +18 -0
- package/lib/platform/aws/aws-get-credentials.js +28 -0
- package/lib/platform/aws/aws-get-default-region.js +26 -0
- package/lib/platform/aws/aws-whoami.js +15 -0
- package/lib/platform/aws/constants.js +7 -0
- package/lib/platform/aws/iam-cf-templates/aws-iam-fargate-cf-template.yml +219 -0
- package/lib/platform/aws/iam-cf-templates/aws-iam-lambda-cf-template.yml +125 -0
- package/lib/platform/aws/iam-cf-templates/gh-oidc-fargate.yml +241 -0
- package/lib/platform/aws/iam-cf-templates/gh-oidc-lambda.yml +153 -0
- package/lib/platform/aws-ecs/ecs.js +247 -0
- package/lib/platform/aws-ecs/legacy/aws-util.js +134 -0
- package/lib/platform/aws-ecs/legacy/bom.js +528 -0
- package/lib/platform/aws-ecs/legacy/constants.js +27 -0
- package/lib/platform/aws-ecs/legacy/create-s3-client.js +24 -0
- package/lib/platform/aws-ecs/legacy/create-test.js +247 -0
- package/lib/platform/aws-ecs/legacy/errors.js +34 -0
- package/lib/platform/aws-ecs/legacy/find-public-subnets.js +149 -0
- package/lib/platform/aws-ecs/legacy/plugins/artillery-plugin-inspect-script/index.js +27 -0
- package/lib/platform/aws-ecs/legacy/plugins/artillery-plugin-sqs-reporter/azure-aqs.js +80 -0
- package/lib/platform/aws-ecs/legacy/plugins/artillery-plugin-sqs-reporter/index.js +202 -0
- package/lib/platform/aws-ecs/legacy/plugins.js +16 -0
- package/lib/platform/aws-ecs/legacy/run-cluster.js +1994 -0
- package/lib/platform/aws-ecs/legacy/sqs-reporter.js +401 -0
- package/lib/platform/aws-ecs/legacy/tags.js +22 -0
- package/lib/platform/aws-ecs/legacy/test-run-status.js +9 -0
- package/lib/platform/aws-ecs/legacy/time.js +67 -0
- package/lib/platform/aws-ecs/legacy/util.js +97 -0
- package/lib/platform/aws-ecs/worker/Dockerfile +64 -0
- package/lib/platform/aws-ecs/worker/helpers.sh +80 -0
- package/lib/platform/aws-ecs/worker/loadgen-worker +656 -0
- package/lib/platform/aws-lambda/dependencies.js +130 -0
- package/lib/platform/aws-lambda/index.js +734 -0
- package/lib/platform/aws-lambda/lambda-handler/a9-handler-dependencies.js +73 -0
- package/lib/platform/aws-lambda/lambda-handler/a9-handler-helpers.js +43 -0
- package/lib/platform/aws-lambda/lambda-handler/a9-handler-index.js +235 -0
- package/lib/platform/aws-lambda/lambda-handler/package.json +15 -0
- package/lib/platform/aws-lambda/prices.js +29 -0
- package/lib/platform/az/aci.js +694 -0
- package/lib/platform/az/aqs-queue-consumer.js +88 -0
- package/lib/platform/az/regions.js +52 -0
- package/lib/platform/cloud/api.js +72 -0
- package/lib/platform/cloud/cloud.js +448 -0
- package/lib/platform/cloud/http-client.js +19 -0
- package/lib/platform/local/artillery-worker-local.js +154 -0
- package/lib/platform/local/index.js +174 -0
- package/lib/platform/local/worker.js +261 -0
- package/lib/platform/worker-states.js +13 -0
- package/lib/queue-consumer/index.js +56 -0
- package/lib/stash.js +41 -0
- package/lib/telemetry.js +78 -0
- package/lib/util/await-on-ee.js +24 -0
- package/lib/util/generate-id.js +9 -0
- package/lib/util/parse-tag-string.js +21 -0
- package/lib/util/prepare-test-execution-plan.js +216 -0
- package/lib/util/sleep.js +7 -0
- package/lib/util/validate-script.js +132 -0
- package/lib/util.js +294 -0
- package/lib/utils-config.js +31 -0
- package/package.json +323 -0
- package/types.d.ts +317 -0
- package/util.js +1 -0
|
@@ -0,0 +1,694 @@
|
|
|
1
|
+
// Copyright (c) Artillery Software Inc.
|
|
2
|
+
// SPDX-License-Identifier: BUSL-1.1
|
|
3
|
+
//
|
|
4
|
+
// Non-evaluation use of Artillery on Azure requires a commercial license
|
|
5
|
+
|
|
6
|
+
const { QueueConsumer } = require('./aqs-queue-consumer');
|
|
7
|
+
const { SQS_QUEUES_NAME_PREFIX } = require('../aws/constants');
|
|
8
|
+
const { DefaultAzureCredential } = require('@azure/identity');
|
|
9
|
+
const { QueueClient } = require('@azure/storage-queue');
|
|
10
|
+
const {
|
|
11
|
+
ContainerInstanceManagementClient
|
|
12
|
+
} = require('@azure/arm-containerinstance');
|
|
13
|
+
const { BlobServiceClient } = require('@azure/storage-blob');
|
|
14
|
+
const { createTest } = require('../aws-ecs/legacy/create-test');
|
|
15
|
+
const util = require('../aws-ecs/legacy/util');
|
|
16
|
+
const generateId = require('../../util/generate-id');
|
|
17
|
+
const EventEmitter = require('eventemitter3');
|
|
18
|
+
const debug = require('debug')('platform:azure-aci');
|
|
19
|
+
const { IMAGE_VERSION, WAIT_TIMEOUT } = require('../aws-ecs/legacy/constants');
|
|
20
|
+
const { regionNames } = require('./regions');
|
|
21
|
+
const path = require('node:path');
|
|
22
|
+
const { Timeout, sleep } = require('../aws-ecs/legacy/time');
|
|
23
|
+
const dotenv = require('dotenv');
|
|
24
|
+
const fs = require('node:fs');
|
|
25
|
+
const request = require('got');
|
|
26
|
+
|
|
27
|
+
// Helper to convert readable stream to string
|
|
28
|
+
async function streamToString(readableStream) {
|
|
29
|
+
return new Promise((resolve, reject) => {
|
|
30
|
+
const chunks = [];
|
|
31
|
+
readableStream.on('data', (data) => {
|
|
32
|
+
chunks.push(data.toString());
|
|
33
|
+
});
|
|
34
|
+
readableStream.on('end', () => {
|
|
35
|
+
resolve(chunks.join(''));
|
|
36
|
+
});
|
|
37
|
+
readableStream.on('error', reject);
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
class PlatformAzureACI {
|
|
42
|
+
constructor(script, variablePayload, opts, platformOpts) {
|
|
43
|
+
this.script = script;
|
|
44
|
+
this.variablePayload = variablePayload;
|
|
45
|
+
this.opts = opts;
|
|
46
|
+
this.platformOpts = platformOpts;
|
|
47
|
+
|
|
48
|
+
this.cloudKey =
|
|
49
|
+
this.platformOpts.cliArgs.key || process.env.ARTILLERY_CLOUD_API_KEY;
|
|
50
|
+
|
|
51
|
+
this.events = new EventEmitter();
|
|
52
|
+
|
|
53
|
+
this.testRunId = platformOpts.testRunId;
|
|
54
|
+
|
|
55
|
+
this.workers = {};
|
|
56
|
+
this.count = 0;
|
|
57
|
+
this.waitingReadyCount = 0;
|
|
58
|
+
this.artilleryArgs = [];
|
|
59
|
+
|
|
60
|
+
this.azureTenantId =
|
|
61
|
+
process.env.AZURE_TENANT_ID || platformOpts.platformConfig['tenant-id'];
|
|
62
|
+
this.azureSubscriptionId =
|
|
63
|
+
process.env.AZURE_SUBSCRIPTION_ID ||
|
|
64
|
+
platformOpts.platformConfig['subscription-id'];
|
|
65
|
+
this.azureClientId = process.env.AZURE_CLIENT_ID;
|
|
66
|
+
this.azureClientSecret = process.env.AZURE_CLIENT_SECRET;
|
|
67
|
+
|
|
68
|
+
this.storageAccount =
|
|
69
|
+
process.env.AZURE_STORAGE_ACCOUNT ||
|
|
70
|
+
platformOpts.platformConfig['storage-account'];
|
|
71
|
+
this.blobContainerName =
|
|
72
|
+
process.env.AZURE_STORAGE_BLOB_CONTAINER ||
|
|
73
|
+
platformOpts.platformConfig['blob-container'];
|
|
74
|
+
this.resourceGroupName =
|
|
75
|
+
process.env.AZURE_RESOURCE_GROUP ||
|
|
76
|
+
platformOpts.platformConfig['resource-group'];
|
|
77
|
+
|
|
78
|
+
this.cpu = parseInt(platformOpts.platformConfig.cpu, 10) || 4;
|
|
79
|
+
this.memory = parseInt(platformOpts.platformConfig.memory, 10) || 8;
|
|
80
|
+
this.region = platformOpts.platformConfig.region || 'eastus';
|
|
81
|
+
|
|
82
|
+
this.extraEnvVars = {};
|
|
83
|
+
|
|
84
|
+
if (!regionNames.includes(this.region)) {
|
|
85
|
+
const err = new Error(`Invalid region: ${this.region}`);
|
|
86
|
+
err.code = 'INVALID_REGION';
|
|
87
|
+
err.url = 'https://docs.art/az/regions';
|
|
88
|
+
throw err;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (
|
|
92
|
+
!this.azureTenantId ||
|
|
93
|
+
!this.azureSubscriptionId ||
|
|
94
|
+
!this.azureClientId ||
|
|
95
|
+
!this.azureClientSecret
|
|
96
|
+
) {
|
|
97
|
+
const err = new Error('Azure credentials not found');
|
|
98
|
+
err.code = 'AZURE_CREDENTIALS_NOT_FOUND';
|
|
99
|
+
err.url = 'https://docs.art/az/credentials';
|
|
100
|
+
throw err;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (
|
|
104
|
+
!this.storageAccount ||
|
|
105
|
+
!this.blobContainerName ||
|
|
106
|
+
!this.resourceGroupName
|
|
107
|
+
) {
|
|
108
|
+
const err = new Error('Azure configuration not found');
|
|
109
|
+
err.code = 'AZURE_CONFIG_NOT_FOUND';
|
|
110
|
+
err.url = 'https://docs.art/az/configuration';
|
|
111
|
+
throw err;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
this.containerInstances = [];
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async init() {
|
|
118
|
+
const credential = new DefaultAzureCredential();
|
|
119
|
+
|
|
120
|
+
artillery.log('Tenant ID:', this.azureTenantId);
|
|
121
|
+
artillery.log('Subscription ID:', this.azureSubscriptionId);
|
|
122
|
+
artillery.log('Storage account:', this.storageAccount);
|
|
123
|
+
artillery.log('Blob container:', this.blobContainerName);
|
|
124
|
+
artillery.log('Resource group:', this.resourceGroupName);
|
|
125
|
+
|
|
126
|
+
if (this.platformOpts.count > 5) {
|
|
127
|
+
const ok = await this.checkLicense();
|
|
128
|
+
if (!ok) {
|
|
129
|
+
console.log();
|
|
130
|
+
console.log(`
|
|
131
|
+
+--------------------------------------------------+
|
|
132
|
+
| License for Azure integration not found |
|
|
133
|
+
| |
|
|
134
|
+
| Load tests on Azure are limited to a maximum of |
|
|
135
|
+
| 5 workers without a valid license. |
|
|
136
|
+
| See https://docs.art/az/license for more details |
|
|
137
|
+
+--------------------------------------------------+
|
|
138
|
+
`);
|
|
139
|
+
throw new Error('ERR_LICENSE_REQUIRED');
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
//
|
|
143
|
+
// Upload test bundle
|
|
144
|
+
//
|
|
145
|
+
|
|
146
|
+
this.blobServiceClient = new BlobServiceClient(
|
|
147
|
+
`https://${this.storageAccount}.blob.core.windows.net`,
|
|
148
|
+
credential
|
|
149
|
+
);
|
|
150
|
+
this.blobContainerClient = this.blobServiceClient.getContainerClient(
|
|
151
|
+
this.blobContainerName
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
const customSyncClient = {
|
|
155
|
+
send: async (command) => {
|
|
156
|
+
// command is always an instance of PutObjectCommand() from @aws-sdk/client-s3
|
|
157
|
+
const { Key, Body } = command.input;
|
|
158
|
+
const blockBlobClient =
|
|
159
|
+
this.blobContainerClient.getBlockBlobClient(Key);
|
|
160
|
+
await blockBlobClient.upload(Body, Body.length);
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
const { manifest } = await createTest(this.opts.absoluteScriptPath, {
|
|
165
|
+
name: this.testRunId,
|
|
166
|
+
config: this.platformOpts.cliArgs.config,
|
|
167
|
+
flags: this.platformOpts.cliArgs,
|
|
168
|
+
customSyncClient
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
//
|
|
172
|
+
// Create the queue
|
|
173
|
+
//
|
|
174
|
+
this.queueName = `${SQS_QUEUES_NAME_PREFIX}_${this.testRunId}.`
|
|
175
|
+
.replaceAll('_', '-')
|
|
176
|
+
.slice(0, 63);
|
|
177
|
+
this.queueUrl =
|
|
178
|
+
process.env.AZURE_STORAGE_QUEUE_URL ||
|
|
179
|
+
`https://${this.storageAccount}.queue.core.windows.net/${this.queueName}`;
|
|
180
|
+
const queueClient = new QueueClient(this.queueUrl, credential);
|
|
181
|
+
await queueClient.create();
|
|
182
|
+
this.aqsClient = queueClient;
|
|
183
|
+
|
|
184
|
+
// Construct CLI args for the container
|
|
185
|
+
|
|
186
|
+
this.artilleryArgs = [];
|
|
187
|
+
this.artilleryArgs.push('run');
|
|
188
|
+
|
|
189
|
+
if (this.platformOpts.cliArgs.environment) {
|
|
190
|
+
this.artilleryArgs.push('-e');
|
|
191
|
+
this.artilleryArgs.push(this.platformOpts.cliArgs.environment);
|
|
192
|
+
}
|
|
193
|
+
if (this.platformOpts.cliArgs.solo) {
|
|
194
|
+
this.artilleryArgs.push('--solo');
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (this.platformOpts.cliArgs.target) {
|
|
198
|
+
this.artilleryArgs.push('--target');
|
|
199
|
+
this.artilleryArgs.push(this.platformOpts.cliArgs.target);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (this.platformOpts.cliArgs.variables) {
|
|
203
|
+
this.artilleryArgs.push('-v');
|
|
204
|
+
this.artilleryArgs.push(this.platformOpts.cliArgs.variables);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (this.platformOpts.cliArgs.overrides) {
|
|
208
|
+
this.artilleryArgs.push('--overrides');
|
|
209
|
+
this.artilleryArgs.push(this.platformOpts.cliArgs.overrides);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (this.platformOpts.cliArgs.dotenv) {
|
|
213
|
+
const dotEnvPath = path.resolve(
|
|
214
|
+
process.cwd(),
|
|
215
|
+
this.platformOpts.cliArgs.dotenv
|
|
216
|
+
);
|
|
217
|
+
const contents = fs.readFileSync(dotEnvPath);
|
|
218
|
+
const envVars = dotenv.parse(contents);
|
|
219
|
+
this.extraEnvVars = Object.assign({}, this.extraEnvVars, envVars);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (this.platformOpts.cliArgs['scenario-name']) {
|
|
223
|
+
this.artilleryArgs.push('--scenario-name');
|
|
224
|
+
this.artilleryArgs.push(this.platformOpts.cliArgs['scenario-name']);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (this.platformOpts.cliArgs.config) {
|
|
228
|
+
this.artilleryArgs.push('--config');
|
|
229
|
+
const p = manifest.files.filter(
|
|
230
|
+
(x) => x.orig === this.opts.absoluteConfigPath
|
|
231
|
+
)[0];
|
|
232
|
+
this.artilleryArgs.push(p.noPrefixPosix);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (this.platformOpts.cliArgs.quiet) {
|
|
236
|
+
this.artilleryArgs.push('--quiet');
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// This needs to be the last argument for now:
|
|
240
|
+
const p = manifest.files.filter(
|
|
241
|
+
(x) => x.orig === this.opts.absoluteScriptPath
|
|
242
|
+
)[0];
|
|
243
|
+
this.artilleryArgs.push(p.noPrefixPosix);
|
|
244
|
+
|
|
245
|
+
const poolSize =
|
|
246
|
+
typeof process.env.CONSUMER_POOL_SIZE !== 'undefined'
|
|
247
|
+
? parseInt(process.env.CONSUMER_POOL_SIZE, 10)
|
|
248
|
+
: Math.max(Math.ceil(this.count / 25), 5);
|
|
249
|
+
|
|
250
|
+
const consumer = new QueueConsumer(
|
|
251
|
+
{ poolSize },
|
|
252
|
+
{
|
|
253
|
+
queueUrl: process.env.AZURE_STORAGE_QUEUE_URL || this.queueUrl,
|
|
254
|
+
handleMessage: async (message) => {
|
|
255
|
+
let payload = null;
|
|
256
|
+
let attributes = null;
|
|
257
|
+
try {
|
|
258
|
+
const result = JSON.parse(message.Body);
|
|
259
|
+
payload = result.payload;
|
|
260
|
+
attributes = result.attributes;
|
|
261
|
+
} catch (parseErr) {
|
|
262
|
+
console.error(parseErr);
|
|
263
|
+
console.error(message.Body);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (process.env.LOG_QUEUE_MESSAGES) {
|
|
267
|
+
console.log(message);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (!payload) {
|
|
271
|
+
throw new Error('AQS message with an empty body');
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Handle overflow messages stored in blob storage
|
|
275
|
+
if (payload._overflowRef) {
|
|
276
|
+
try {
|
|
277
|
+
const blobClient =
|
|
278
|
+
this.blobContainerClient.getBlockBlobClient(
|
|
279
|
+
payload._overflowRef
|
|
280
|
+
);
|
|
281
|
+
const downloadResponse = await blobClient.download(0);
|
|
282
|
+
const downloaded = await streamToString(
|
|
283
|
+
downloadResponse.readableStreamBody
|
|
284
|
+
);
|
|
285
|
+
const fullMessage = JSON.parse(downloaded);
|
|
286
|
+
payload = fullMessage.payload;
|
|
287
|
+
attributes = fullMessage.attributes;
|
|
288
|
+
} catch (blobErr) {
|
|
289
|
+
console.error('Failed to fetch worker message:', blobErr);
|
|
290
|
+
throw new Error(
|
|
291
|
+
`Failed to fetch worker message: ${payload._overflowRef}`
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (!attributes || !attributes.testId || !attributes.workerId) {
|
|
297
|
+
throw new Error('AQS message with no testId or workerId');
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (this.testRunId !== attributes.testId) {
|
|
301
|
+
throw new Error('AQS message for an unknown testId');
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const workerId = attributes.workerId;
|
|
305
|
+
if (payload.event === 'workerStats') {
|
|
306
|
+
this.events.emit('stats', workerId, payload);
|
|
307
|
+
} else if (payload.event === 'artillery.log') {
|
|
308
|
+
console.log(payload.log);
|
|
309
|
+
} else if (payload.event === 'done') {
|
|
310
|
+
// 'done' handler in Launcher exects the message argument to have an "id" and "report" fields
|
|
311
|
+
payload.id = workerId;
|
|
312
|
+
payload.report = payload.stats;
|
|
313
|
+
this.events.emit('done', workerId, payload);
|
|
314
|
+
} else if (
|
|
315
|
+
payload.event === 'phaseStarted' ||
|
|
316
|
+
payload.event === 'phaseCompleted'
|
|
317
|
+
) {
|
|
318
|
+
payload.id = workerId;
|
|
319
|
+
this.events.emit(payload.event, workerId, { phase: payload.phase });
|
|
320
|
+
} else if (payload.event === 'workerError') {
|
|
321
|
+
global.artillery.suggestedExitCode = payload.exitCode || 1;
|
|
322
|
+
|
|
323
|
+
if (payload.exitCode !== 21) {
|
|
324
|
+
this.events.emit(payload.event, workerId, {
|
|
325
|
+
id: workerId,
|
|
326
|
+
error: new Error(
|
|
327
|
+
`A worker has exited with an error. Reason: ${payload.reason}`
|
|
328
|
+
),
|
|
329
|
+
level: 'error',
|
|
330
|
+
aggregatable: false,
|
|
331
|
+
logs: payload.logs
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
} else if (payload.event === 'workerReady') {
|
|
335
|
+
this.events.emit(payload.event, workerId);
|
|
336
|
+
this.waitingReadyCount++;
|
|
337
|
+
|
|
338
|
+
// TODO: Do this only for batches of workers with "wait" option set
|
|
339
|
+
if (this.waitingReadyCount === this.count) {
|
|
340
|
+
await this.sendGoSignal();
|
|
341
|
+
}
|
|
342
|
+
} else {
|
|
343
|
+
debug(payload);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
);
|
|
348
|
+
|
|
349
|
+
consumer.on('error', (err) => {
|
|
350
|
+
console.error(err);
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
this.queueConsumer = consumer;
|
|
354
|
+
|
|
355
|
+
const metadata = {
|
|
356
|
+
region: this.region,
|
|
357
|
+
platformConfig: {
|
|
358
|
+
memory: this.memory,
|
|
359
|
+
cpu: this.cpu
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
global.artillery.globalEvents.emit('metadata', metadata);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
getDesiredWorkerCount() {
|
|
366
|
+
return this.platformOpts.count;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
async startJob() {
|
|
370
|
+
await this.init();
|
|
371
|
+
|
|
372
|
+
console.log('Creating container instances...');
|
|
373
|
+
|
|
374
|
+
// Create & run the leader:
|
|
375
|
+
const { workerId } = await this.createWorker();
|
|
376
|
+
this.workers[workerId] = { workerId };
|
|
377
|
+
await this.runWorker(workerId, { isLeader: true });
|
|
378
|
+
|
|
379
|
+
// Run the rest of the containers we need:
|
|
380
|
+
for (let i = 0; i < this.platformOpts.count - 1; i++) {
|
|
381
|
+
const { workerId } = await this.createWorker();
|
|
382
|
+
this.workers[workerId] = { workerId };
|
|
383
|
+
await this.runWorker(workerId);
|
|
384
|
+
|
|
385
|
+
if (i > 0 && i % 10 === 0) {
|
|
386
|
+
const delayMs =
|
|
387
|
+
Math.floor(
|
|
388
|
+
Math.random() *
|
|
389
|
+
parseInt(process.env.AZURE_LAUNCH_STAGGER_SEC || '5', 10)
|
|
390
|
+
) * 1000;
|
|
391
|
+
await sleep(delayMs);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
let instancesCreated = false;
|
|
396
|
+
console.log('Waiting for Azure ACI to create container instances...');
|
|
397
|
+
|
|
398
|
+
const containerInstanceClient = new ContainerInstanceManagementClient(
|
|
399
|
+
new DefaultAzureCredential(),
|
|
400
|
+
this.azureSubscriptionId
|
|
401
|
+
);
|
|
402
|
+
|
|
403
|
+
const provisioningWaitTimeout = new Timeout(WAIT_TIMEOUT * 1000).start();
|
|
404
|
+
|
|
405
|
+
let containerGroupsInTestRun = [];
|
|
406
|
+
while (true) {
|
|
407
|
+
const containerGroupListResult =
|
|
408
|
+
containerInstanceClient.containerGroups.listByResourceGroup(
|
|
409
|
+
this.resourceGroupName
|
|
410
|
+
);
|
|
411
|
+
|
|
412
|
+
containerGroupsInTestRun = [];
|
|
413
|
+
for await (const containerGroup of containerGroupListResult) {
|
|
414
|
+
if (containerGroup.name.indexOf(this.testRunId) > 0) {
|
|
415
|
+
containerGroupsInTestRun.push(containerGroup);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
const byStatus = containerGroupsInTestRun.reduce((acc, cg) => {
|
|
420
|
+
if (!acc[cg.provisioningState]) {
|
|
421
|
+
acc[cg.provisioningState] = 0;
|
|
422
|
+
}
|
|
423
|
+
acc[cg.provisioningState]++;
|
|
424
|
+
return acc;
|
|
425
|
+
}, {});
|
|
426
|
+
|
|
427
|
+
if (
|
|
428
|
+
(byStatus.Succeeded || 0) + (byStatus.Running || 0) ===
|
|
429
|
+
this.count
|
|
430
|
+
) {
|
|
431
|
+
instancesCreated = true;
|
|
432
|
+
break;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
if (provisioningWaitTimeout.timedout()) {
|
|
436
|
+
break;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
await sleep(10000);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
if (instancesCreated) {
|
|
443
|
+
console.log(
|
|
444
|
+
'Container instances have been created. Waiting for workers to start...'
|
|
445
|
+
);
|
|
446
|
+
await this.queueConsumer.start();
|
|
447
|
+
} else {
|
|
448
|
+
console.log('Some containers instances failed to provision');
|
|
449
|
+
console.log('Please see the Azure console for details');
|
|
450
|
+
console.log(
|
|
451
|
+
'https://portal.azure.com/#view/HubsExtension/BrowseResource/resourceType/Microsoft.ContainerInstance%2FcontainerGroups'
|
|
452
|
+
);
|
|
453
|
+
await global.artillery.shutdown();
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
async shutdown() {
|
|
458
|
+
this.queueConsumer.stop();
|
|
459
|
+
try {
|
|
460
|
+
await this.aqsClient.delete();
|
|
461
|
+
} catch (_err) {}
|
|
462
|
+
|
|
463
|
+
const credential = new DefaultAzureCredential();
|
|
464
|
+
|
|
465
|
+
if (process.env.RETAIN_CONTAINER_INSTANCES !== 'true') {
|
|
466
|
+
const containerInstanceClient = new ContainerInstanceManagementClient(
|
|
467
|
+
credential,
|
|
468
|
+
this.azureSubscriptionId
|
|
469
|
+
);
|
|
470
|
+
|
|
471
|
+
const containerGroupListResult =
|
|
472
|
+
containerInstanceClient.containerGroups.listByResourceGroup(
|
|
473
|
+
this.resourceGroupName
|
|
474
|
+
);
|
|
475
|
+
|
|
476
|
+
for await (const containerGroup of containerGroupListResult) {
|
|
477
|
+
if (containerGroup.name.indexOf(this.testRunId) > 0) {
|
|
478
|
+
try {
|
|
479
|
+
await containerInstanceClient.containerGroups.beginDeleteAndWait(
|
|
480
|
+
this.resourceGroupName,
|
|
481
|
+
containerGroup.name
|
|
482
|
+
);
|
|
483
|
+
} catch (err) {
|
|
484
|
+
console.log(err);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
async sendGoSignal() {
|
|
492
|
+
const Key = `tests/${this.testRunId}/go.json`;
|
|
493
|
+
const blockBlobClient = this.blobContainerClient.getBlockBlobClient(Key);
|
|
494
|
+
const _res = await blockBlobClient.upload('', 0);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
async createWorker() {
|
|
498
|
+
const workerId = generateId('worker');
|
|
499
|
+
return { workerId };
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
async runWorker(workerId, opts = { isLeader: false }) {
|
|
503
|
+
const credential = new DefaultAzureCredential();
|
|
504
|
+
|
|
505
|
+
const imageVersion =
|
|
506
|
+
process.env.ARTILLERY_WORKER_IMAGE_VERSION || IMAGE_VERSION;
|
|
507
|
+
const defaultArchitecture = 'x86_64';
|
|
508
|
+
const containerImageURL =
|
|
509
|
+
process.env.WORKER_IMAGE_URL ||
|
|
510
|
+
`public.ecr.aws/d8a4z9o5/artillery-worker:${imageVersion}-${defaultArchitecture}`;
|
|
511
|
+
|
|
512
|
+
const client = new ContainerInstanceManagementClient(
|
|
513
|
+
credential,
|
|
514
|
+
this.azureSubscriptionId
|
|
515
|
+
);
|
|
516
|
+
|
|
517
|
+
const environmentVariables = [
|
|
518
|
+
{
|
|
519
|
+
name: 'WORKER_ID_OVERRIDE',
|
|
520
|
+
value: workerId
|
|
521
|
+
},
|
|
522
|
+
{
|
|
523
|
+
name: 'ARTILLERY_TEST_RUN_ID',
|
|
524
|
+
value: this.testRunId
|
|
525
|
+
},
|
|
526
|
+
// {
|
|
527
|
+
// name: 'DEBUGX',
|
|
528
|
+
// value: 'true',
|
|
529
|
+
// },
|
|
530
|
+
{
|
|
531
|
+
name: 'DEBUG',
|
|
532
|
+
value: 'cloud'
|
|
533
|
+
},
|
|
534
|
+
{
|
|
535
|
+
name: 'IS_LEADER',
|
|
536
|
+
value: String(opts.isLeader)
|
|
537
|
+
},
|
|
538
|
+
{
|
|
539
|
+
name: 'AQS_QUEUE_NAME',
|
|
540
|
+
value: this.queueName
|
|
541
|
+
},
|
|
542
|
+
{
|
|
543
|
+
name: 'AZURE_STORAGE_ACCOUNT',
|
|
544
|
+
value: this.storageAccount
|
|
545
|
+
},
|
|
546
|
+
{
|
|
547
|
+
name: 'AZURE_STORAGE_BLOB_CONTAINER',
|
|
548
|
+
value: this.blobContainerName
|
|
549
|
+
},
|
|
550
|
+
{
|
|
551
|
+
name: 'AZURE_SUBSCRIPTION_ID',
|
|
552
|
+
secureValue: this.azureSubscriptionId
|
|
553
|
+
},
|
|
554
|
+
{
|
|
555
|
+
name: 'AZURE_TENANT_ID',
|
|
556
|
+
secureValue: this.azureTenantId
|
|
557
|
+
},
|
|
558
|
+
{
|
|
559
|
+
name: 'AZURE_CLIENT_ID',
|
|
560
|
+
secureValue: this.azureClientId
|
|
561
|
+
},
|
|
562
|
+
{
|
|
563
|
+
name: 'AZURE_CLIENT_SECRET',
|
|
564
|
+
secureValue: this.azureClientSecret
|
|
565
|
+
},
|
|
566
|
+
{
|
|
567
|
+
name: 'AZURE_STORAGE_AUTH_MODE',
|
|
568
|
+
value: 'login'
|
|
569
|
+
}
|
|
570
|
+
];
|
|
571
|
+
|
|
572
|
+
if (this.cloudKey) {
|
|
573
|
+
environmentVariables.push({
|
|
574
|
+
name: 'ARTILLERY_CLOUD_API_KEY',
|
|
575
|
+
secureValue: this.cloudKey
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
const cloudEndpoint = process.env.ARTILLERY_CLOUD_ENDPOINT;
|
|
580
|
+
if (cloudEndpoint) {
|
|
581
|
+
environmentVariables.push({
|
|
582
|
+
name: 'ARTILLERY_CLOUD_ENDPOINT',
|
|
583
|
+
secureValue: cloudEndpoint
|
|
584
|
+
});
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
for (const [name, value] of Object.entries(this.extraEnvVars)) {
|
|
588
|
+
environmentVariables.push({ name, value });
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
const containerGroup = {
|
|
592
|
+
location: this.region,
|
|
593
|
+
containers: [
|
|
594
|
+
{
|
|
595
|
+
name: 'artillery-worker',
|
|
596
|
+
image: containerImageURL,
|
|
597
|
+
resources: {
|
|
598
|
+
requests: {
|
|
599
|
+
cpu: this.cpu,
|
|
600
|
+
memoryInGB: this.memory
|
|
601
|
+
}
|
|
602
|
+
},
|
|
603
|
+
command: [
|
|
604
|
+
'/artillery/loadgen-worker',
|
|
605
|
+
'-z',
|
|
606
|
+
'yes', // yes for Azure
|
|
607
|
+
'-q',
|
|
608
|
+
this.queueUrl,
|
|
609
|
+
'-p',
|
|
610
|
+
this.blobContainerName,
|
|
611
|
+
'-a',
|
|
612
|
+
util.btoa(JSON.stringify(this.artilleryArgs)),
|
|
613
|
+
'-i',
|
|
614
|
+
this.testRunId,
|
|
615
|
+
'-t',
|
|
616
|
+
String(WAIT_TIMEOUT),
|
|
617
|
+
'-d',
|
|
618
|
+
'NOT_USED_ON_AZURE',
|
|
619
|
+
'-r',
|
|
620
|
+
'NOT_USED_ON_AZURE'
|
|
621
|
+
],
|
|
622
|
+
environmentVariables
|
|
623
|
+
}
|
|
624
|
+
],
|
|
625
|
+
osType: 'Linux',
|
|
626
|
+
restartPolicy: 'Never'
|
|
627
|
+
};
|
|
628
|
+
|
|
629
|
+
if (!this.ts) {
|
|
630
|
+
this.ts = Date.now();
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
const containerGroupName = `artillery-test-${this.ts}-${this.testRunId}-${this.count}`;
|
|
634
|
+
try {
|
|
635
|
+
const containerInstance =
|
|
636
|
+
await client.containerGroups.beginCreateOrUpdate(
|
|
637
|
+
this.resourceGroupName,
|
|
638
|
+
containerGroupName,
|
|
639
|
+
containerGroup
|
|
640
|
+
);
|
|
641
|
+
|
|
642
|
+
this.containerInstances.push(containerInstance);
|
|
643
|
+
|
|
644
|
+
this.count++;
|
|
645
|
+
} catch (err) {
|
|
646
|
+
// TODO: Make this better
|
|
647
|
+
console.log(err.code);
|
|
648
|
+
console.log(err.details?.error?.message);
|
|
649
|
+
throw err;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
async stopWorker(_workerId) {}
|
|
654
|
+
|
|
655
|
+
async checkLicense() {
|
|
656
|
+
const baseUrl =
|
|
657
|
+
process.env.ARTILLERY_CLOUD_ENDPOINT || 'https://app.artillery.io';
|
|
658
|
+
const res = await request.get(`${baseUrl}/api/user/whoami`, {
|
|
659
|
+
headers: {
|
|
660
|
+
'x-auth-token': this.cloudKey
|
|
661
|
+
},
|
|
662
|
+
throwHttpErrors: false,
|
|
663
|
+
retry: {
|
|
664
|
+
limit: 3
|
|
665
|
+
}
|
|
666
|
+
});
|
|
667
|
+
|
|
668
|
+
try {
|
|
669
|
+
const body = JSON.parse(res.body);
|
|
670
|
+
const activeOrg = body.activeOrg;
|
|
671
|
+
if (!activeOrg) {
|
|
672
|
+
return false;
|
|
673
|
+
}
|
|
674
|
+
if (!Array.isArray(body.memberships)) {
|
|
675
|
+
return false;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
const activeMembership = body.memberships.find(
|
|
679
|
+
(membership) => membership.id === activeOrg
|
|
680
|
+
);
|
|
681
|
+
|
|
682
|
+
if (!activeMembership) {
|
|
683
|
+
return false;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
const plan = activeMembership.plan;
|
|
687
|
+
return plan !== 'developer';
|
|
688
|
+
} catch (_err) {
|
|
689
|
+
return false;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
module.exports = PlatformAzureACI;
|