1 | 'use strict';
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 | const aws = require('aws-sdk');
|
8 | const debug = require('debug')('engine:lambda');
|
9 | const A = require('async');
|
10 | const _ = require('lodash');
|
11 | const helpers = require('artillery/core/lib/engine_util');
|
12 |
|
13 | const utils = require('./utils');
|
14 |
|
15 | function LambdaEngine (script, ee) {
|
16 | this.script = script;
|
17 | this.ee = ee;
|
18 | this.helpers = helpers;
|
19 | this.config = script.config;
|
20 |
|
21 | this.config.processor = this.config.processor || {};
|
22 |
|
23 | return this;
|
24 | }
|
25 |
|
26 | LambdaEngine.prototype.createScenario = function createScenario (scenarioSpec, ee) {
|
27 |
|
28 |
|
29 |
|
30 | const beforeScenarioFns = _.map(
|
31 | scenarioSpec.beforeScenario,
|
32 | function(hookFunctionName) {
|
33 | return {'function': hookFunctionName};
|
34 | });
|
35 | const afterScenarioFns = _.map(
|
36 | scenarioSpec.afterScenario,
|
37 | function(hookFunctionName) {
|
38 | return {'function': hookFunctionName};
|
39 | });
|
40 |
|
41 | const newFlow = beforeScenarioFns.concat(
|
42 | scenarioSpec.flow.concat(afterScenarioFns));
|
43 |
|
44 | scenarioSpec.flow = newFlow;
|
45 |
|
46 | const tasks = scenarioSpec.flow.map(rs => this.step(rs, ee, {
|
47 | beforeRequest: scenarioSpec.beforeRequest,
|
48 | afterResponse: scenarioSpec.afterResponse,
|
49 | }));
|
50 |
|
51 | return this.compile(tasks, scenarioSpec.flow, ee);
|
52 | };
|
53 |
|
54 | LambdaEngine.prototype.step = function step (rs, ee, opts) {
|
55 | opts = opts || {};
|
56 | let self = this;
|
57 |
|
58 | if (rs.loop) {
|
59 | let steps = _.map(rs.loop, function (rs) {
|
60 | return self.step(rs, ee, opts);
|
61 | });
|
62 |
|
63 | return this.helpers.createLoopWithCount(
|
64 | rs.count || -1,
|
65 | steps,
|
66 | {
|
67 | loopValue: rs.loopValue || '$loopCount',
|
68 | overValues: rs.over,
|
69 | whileTrue: self.config.processor
|
70 | ? self.config.processor[rs.whileTrue] : undefined
|
71 | });
|
72 | }
|
73 |
|
74 | if (rs.log) {
|
75 | return function log (context, callback) {
|
76 | return process.nextTick(function () { callback(null, context); });
|
77 | };
|
78 | }
|
79 |
|
80 | if (rs.think) {
|
81 | return this.helpers.createThink(rs, _.get(self.config, 'defaults.think', {}));
|
82 | }
|
83 |
|
84 | if (rs.function) {
|
85 | return function (context, callback) {
|
86 | let func = self.config.processor[rs.function];
|
87 | if (!func) {
|
88 | return process.nextTick(function () { callback(null, context); });
|
89 | }
|
90 |
|
91 | return func(context, ee, function () {
|
92 | return callback(null, context);
|
93 | });
|
94 | };
|
95 | }
|
96 |
|
97 | if (rs.invoke) {
|
98 | return function invoke (context, callback) {
|
99 |
|
100 | context.funcs.$increment = self.$increment;
|
101 | context.funcs.$decrement = self.$decrement;
|
102 | context.funcs.$contextUid = function () {
|
103 | return context._uid;
|
104 | };
|
105 |
|
106 | const payload = typeof rs.invoke.payload === 'object'
|
107 | ? JSON.stringify(rs.invoke.payload)
|
108 | : String(rs.invoke.payload);
|
109 |
|
110 |
|
111 |
|
112 | var awsParams = {
|
113 | ClientContext: Buffer.from(rs.invoke.clientContext || '{}').toString('base64'),
|
114 | FunctionName: rs.invoke.target || self.script.config.target,
|
115 | InvocationType: rs.invoke.invocationType || 'Event',
|
116 | LogType: rs.invoke.logType || 'Tail',
|
117 | Payload: helpers.template(payload, context),
|
118 | Qualifier: rs.invoke.qualifier || '$LATEST'
|
119 | };
|
120 |
|
121 |
|
122 |
|
123 |
|
124 | const params = _.assign({
|
125 | url: context.lambda.endpoint.href,
|
126 | awsParams: awsParams,
|
127 | }, rs.invoke);
|
128 |
|
129 |
|
130 | const beforeRequestFunctionNames = _.concat(opts.beforeRequest || [], rs.invoke.beforeRequest || []);
|
131 |
|
132 | utils.processBeforeRequestFunctions(
|
133 | self.script,
|
134 | beforeRequestFunctionNames,
|
135 | params,
|
136 | context,
|
137 | ee,
|
138 | function done(err) {
|
139 | if (err) {
|
140 | debug(err);
|
141 | return callback(err, context);
|
142 | }
|
143 |
|
144 | ee.emit('request');
|
145 | const startedAt = process.hrtime();
|
146 |
|
147 |
|
148 |
|
149 |
|
150 | awsParams.Payload = helpers.template(payload, context);
|
151 |
|
152 |
|
153 | context.lambda.invoke(awsParams, function (err, data) {
|
154 |
|
155 | if (err) {
|
156 | debug(err);
|
157 | ee.emit('error', err);
|
158 | return callback(err, context);
|
159 | }
|
160 |
|
161 | let code = data.StatusCode || 0;
|
162 | const endedAt = process.hrtime(startedAt);
|
163 | let delta = (endedAt[0] * 1e9) + endedAt[1];
|
164 | ee.emit('response', delta, code, context._uid);
|
165 | debug(data);
|
166 |
|
167 |
|
168 |
|
169 | const payload = utils.tryToParse(data.Payload);
|
170 |
|
171 |
|
172 |
|
173 |
|
174 | const response = {
|
175 | body: payload.body,
|
176 | statusCode: data.StatusCode,
|
177 | headers: {
|
178 | 'content-type': payload.contentType
|
179 | },
|
180 | };
|
181 |
|
182 | helpers.captureOrMatch(
|
183 | params,
|
184 | response,
|
185 | context,
|
186 | function captured(err, result) {
|
187 |
|
188 | let haveFailedCaptures = _.some(result.captures, function(v, k) {
|
189 | return v === '';
|
190 | });
|
191 |
|
192 | if (!haveFailedCaptures) {
|
193 | _.each(result.captures, function(v, k) {
|
194 | _.set(context.vars, k, v);
|
195 | });
|
196 | }
|
197 |
|
198 | const afterResponseFunctionNames = _.concat(opts.afterResponse || [], rs.invoke.afterResponse || []);
|
199 |
|
200 | utils.processAfterResponseFunctions(
|
201 | self.script,
|
202 | afterResponseFunctionNames,
|
203 | params,
|
204 | response,
|
205 | context,
|
206 | ee,
|
207 | function done(err) {
|
208 | if (err) {
|
209 | debug(err);
|
210 | return callback(err, context);
|
211 | }
|
212 |
|
213 | return callback(null, context);
|
214 | }
|
215 | );
|
216 | }
|
217 | );
|
218 |
|
219 | });
|
220 | }
|
221 | )
|
222 | };
|
223 | }
|
224 |
|
225 | return function (context, callback) {
|
226 | return callback(null, context);
|
227 | };
|
228 | };
|
229 |
|
230 | LambdaEngine.prototype.compile = function compile (tasks, scenarioSpec, ee) {
|
231 | const self = this;
|
232 | return function scenario (initialContext, callback) {
|
233 | const init = function init (next) {
|
234 | let opts = {
|
235 | region: self.script.config.lambda.region || 'us-east-1'
|
236 | };
|
237 |
|
238 | if (self.script.config.lambda.function) {
|
239 | opts.endpoint = self.script.config.lambda.function;
|
240 | }
|
241 |
|
242 | initialContext.lambda = new aws.Lambda(opts);
|
243 | ee.emit('started');
|
244 | return next(null, initialContext);
|
245 | };
|
246 |
|
247 | let steps = [init].concat(tasks);
|
248 |
|
249 | A.waterfall(
|
250 | steps,
|
251 | function done (err, context) {
|
252 | if (err) {
|
253 | debug(err);
|
254 | }
|
255 |
|
256 | return callback(err, context);
|
257 | });
|
258 | };
|
259 | };
|
260 |
|
261 | LambdaEngine.prototype.$increment = function $increment (value) {
|
262 | let result = Number.isInteger(value) ? value += 1 : NaN;
|
263 | return result;
|
264 | };
|
265 |
|
266 | LambdaEngine.prototype.$decrement = function $decrement (value) {
|
267 | let result = Number.isInteger(value) ? value -= 1 : NaN;
|
268 | return result;
|
269 | };
|
270 |
|
271 | module.exports = LambdaEngine;
|