Taurus pre-request authentication - performance

I've faced a problem using Taurus. Could someone help me, please? I'm trying to simulate 300 users but before sending those 300 users POST requests, I need to generate a token. The token is attached to the request in that way:
- url: http://url?user_token=${auth_token}
Now I have the following scenario:
load_api:
requests:
- once:
- url: https://endpoint/authenticateUser
method: POST
headers:
Content-Type: application/json
body:
username: username
password: pass
generateToken: true
extract-jsonpath:
auth_token:
jsonpath: $.token
label: get_token
- url: http://url/user_token=${auth_token}
method: POST
headers:
Content-Type: application/json
body-file: test_data/body.json
label: sending_300
As you can see, a token is generated for each thread. And I need that it is generated before the script and then the token is attached to the URL as a parameter. I've tried to separate those for two scenarios but in that way variables from one script can't be used in the other. I was also looking at global variables but it seems like that kind of variable can be created only before executing. So, if someone could help me, I'll appreciate your time spent.
EDIT (thank you so much Dmitri T):
Here is a workable script:
execution:
concurrency: 300
scenario: load_test
scenarios:
load_test:
requests:
- if: ${__groovy(ctx.getThreadNum() == 0 && vars.getIteration() == 1,)}
then:
- url: https://url/authenticateUser
method: POST
headers:
Content-Type: application/json
body:
username: username
password: pass
generateToken: true
extract-jsonpath:
auth_token:
jsonpath: $.token
label: get_token
jsr223: props.put('auth_token', vars.get('auth_token'))
else:
- url: http://endpoint?user_token=${__P(auth_token,)}
method: POST
headers:
Content-Type: application/json
body-file: test_data/body.json
label: sending_300_reqs
think-time: 10s # waiter for processing auth request

If you want to generate a token once and share it across 300 threads:
Generate token for 1st thread during the first iteration using If block and convert it to JMeter Property in JSR223 block. The condition for the If block would be:
${__groovy(ctx.getThreadNum() == 0 && vars.getIteration() == 1,)}
and the code for JSR223 block:
props.put('auth_token', vars.get('auth_token'))
check out Top 8 JMeter Java Classes You Should Be Using with Groovy article to learn what these ctx, props and vars words mean
In your 2nd request refer the property using __P() function
http://url/user_token=${__P(auth_token,)}

Related

GCP Workflow: Handling http functions responses other than 200

When calling http endpoint in GCP workflow, only HttpStatus 200 is considered a success.
How to handle other Success Status codes? 201, 202, etc.
Example workflow from samples:
- readItem:
try:
call: http.get
args:
url: https://example.com/someapi
auth:
type: OIDC
result: APIResponse
except:
as: e
steps:
- knownErrors:
switch:
- condition: ${not("HttpError" in e.tags)}
next: connectionProblem
- condition: ${e.code == 404}
next: urlNotFound
- condition: ${e.code == 403}
next: authProblem
- UnhandledException:
raise: ${e}
- urlFound:
return: ${APIResponse.body}
- connectionProblem:
return: "Connection problem; check URL"
- urlNotFound:
return: "Sorry, URL wasn't found"
- authProblem:
return: "Authentication error"
If the api endpoint https://example.com/someapi returns anything other than a 200 status code the connectionProblem is invoked.
This is the same if its a GET or POST request.
What is the best way of handling this?
There is nothing referencing how to handle other 200s statuses in the documentation for Google Workflows so I assume this is not possible without treating them as errors.
This means that in order to do it you are going to need to add an extra step to deal with this status as an error handling strategy, like - condition: ${e.code == 201} for example.
Alternatively you could open a feature request in Google's Issue Tracker so that they can consider implementing different treatements of such status codes or at least so that this is touched on more details in the documentation.
At present response codes >= 400 and <= 599 are considered an error and will raise an exception. i.e. 200s are considered a success and will not.
Alternatively, if you want to trigger an exception handler for return codes in this range (or for any other reason), this can be done by adding an additional step to the try call, for example (illustration only):
main:
steps:
- getStuff:
try:
steps:
- callStep:
call: http.get
args:
url: <SOME URL>
result: r
- checkNotOK:
switch:
- condition: ${r.code == 202}
raise: ${r}
retry:
predicate: ${custom_predicate}
max_retries: 5
backoff:
initial_delay: 2
max_delay: 60
multiplier: 2
custom_predicate:
params: [e]
steps:
- what_to_repeat:
switch:
- condition: ${e.code == 202}
return: true
- otherwise:
return: false

how do you start a workflow from another workflow and retrieve the return value of called workflow

I am testing google workflow and would like to call a workflow from another workflow but as a separate process (not a subworkflow)
I am able to start the execution but currently unable to retrieve the return value. I receive instead an instance of the execution:
{
"argument": "null",
"name": "projects/xxxxxxxxxxxx/locations/us-central1/workflows/child-workflow/executions/9fb4aa01-2585-42e7-a79f-cfb4b57b22d4",
"startTime": "2020-12-09T01:38:07.073406981Z",
"state": "ACTIVE",
"workflowRevisionId": "000003-cf3"
}
parent-workflow.yaml
main:
params: [args]
steps:
- callChild:
call: http.post
args:
url: 'https://workflowexecutions.googleapis.com/v1beta/projects/my-project/locations/us-central1/workflows/child-workflow/executions'
auth:
type: OAuth2
scope: 'https://www.googleapis.com/auth/cloud-platform'
result: callresult
- returnValue:
return: ${callresult.body}
child-workflow.yaml:
- getCurrentTime:
call: http.get
args:
url: https://us-central1-workflowsample.cloudfunctions.net/datetime
result: CurrentDateTime
- readWikipedia:
call: http.get
args:
url: https://en.wikipedia.org/w/api.php
query:
action: opensearch
search: ${CurrentDateTime.body.dayOfTheWeek}
result: WikiResult
- returnOutput:
return: ${WikiResult.body[1]}
also as an added question how can create a dynamic url from a variable. ${} doesn't seem to work there
As Executions are async API calls, you need to POLL for the workflow to see when finished.
You can have the following algorithm:
main:
steps:
- callChild:
call: http.post
args:
url: ${"https://workflowexecutions.googleapis.com/v1beta/projects/"+sys.get_env("GOOGLE_CLOUD_PROJECT_ID")+"/locations/us-central1/workflows/http_bitly_secrets/executions"}
auth:
type: OAuth2
scope: 'https://www.googleapis.com/auth/cloud-platform'
result: workflow
- waitExecution:
call: CloudWorkflowsWaitExecution
args:
execution: ${workflow.body.name}
result: workflow
- returnValue:
return: ${workflow}
CloudWorkflowsWaitExecution:
params: [execution]
steps:
- init:
assign:
- i: 0
- valid_states: ["ACTIVE","STATE_UNSPECIFIED"]
- result:
state: ACTIVE
- check_condition:
switch:
- condition: ${result.state in valid_states AND i<100}
next: iterate
next: exit_loop
- iterate:
steps:
- sleep:
call: sys.sleep
args:
seconds: 10
- process_item:
call: http.get
args:
url: ${"https://workflowexecutions.googleapis.com/v1beta/"+execution}
auth:
type: OAuth2
result: result
- assign_loop:
assign:
- i: ${i+1}
- result: ${result.body}
next: check_condition
- exit_loop:
return: ${result}
What you see here is that we have a CloudWorkflowsWaitExecution subworkflow which will loop 100 times at most, also has a 10 second delay, it will stop when the workflow has finished, and returns the result.
The output is:
argument: 'null'
endTime: '2020-12-09T13:00:11.099830035Z'
name: projects/985596417983/locations/us-central1/workflows/call_another_workflow/executions/05eeefb5-60bb-4b20-84bd-29f6338fa66b
result: '{"argument":"null","endTime":"2020-12-09T13:00:00.976951808Z","name":"projects/985596417983/locations/us-central1/workflows/http_bitly_secrets/executions/2f4b749c-4283-4c6b-b5c6-e04bbcd57230","result":"{\"archived\":false,\"created_at\":\"2020-10-17T11:12:31+0000\",\"custom_bitlinks\":[],\"deeplinks\":[],\"id\":\"j.mp/2SZaSQK\",\"link\":\"//<edited>/2SZaSQK\",\"long_url\":\"https://cloud.google.com/blog\",\"references\":{\"group\":\"https://api-ssl.bitly.com/v4/groups/Bg7eeADYBa9\"},\"tags\":[]}","startTime":"2020-12-09T13:00:00.577579042Z","state":"SUCCEEDED","workflowRevisionId":"000001-478"}'
startTime: '2020-12-09T13:00:00.353800247Z'
state: SUCCEEDED
workflowRevisionId: 000012-cb8
in the result there is a subkey that holds the results from the external Workflow execution.
The best method is now the workflows.executions.run helper method, which formats the request and blocks until the workflow execution has completed:
- run_execution:
try:
call: googleapis.workflowexecutions.v1.projects.locations.workflows.executions.run
args:
workflow_id: ${workflow}
location: ${location} # Defaults to current location
project_id: ${project} # Defaults to current project
argument: ${arguments} # Arguments could be specified inline as a map instead.
result: r1
except:
as: e
steps: ... # handle a failed execution

Taurus. Reusing common scenario with different variables

Taurus allows me combine scenarios like this
scenarios:
create-bob-account:
requests:
- url: http://example.com/account/create
method: POST
body:
account_owner: bob
account_number: 123
create-lisa-account:
requests:
- url: http://example.com/account/create
method: POST
body:
account_owner: lisa
account_number: 321
account-transfer:
requests:
- include-scenario: create-bob-account
- include-scenario: create-bob-account
- url: http://example.com/transfer
method: POST
body:
account_number_from: 123
account_number_to: 321
...
In this example i have duplicate code that creates info about accout bob and lisa. Can i parametrize an create-account scenario? I.e. create script
scenarios:
create-account:
requests:
- url: http://example.com/account/create
method: POST
body:
account_owner: ${owner}
account_number: ${number}
and call it with different variables, something like this
scenarios:
account-transfer:
requests:
- include-scenario:
arugment: bob, 123
create-account
- include-scenario:
arugment: lisa, 321
create-account
- url: http://example.com/transfer
Maybe there is some way to implement this functionality?
The basic idea is to identify scripts that are independent of specific variables and reuse them in various scripts with the variables that are needed.
Thanks!
How about CSV Data Set Config? This way you can put your account names and IDs into a CSV file and include the request block to read these name/id combination from the CSV file, the relevant Taurus directives are data-sources and include-scenarios
Example YAML config:
execution:
- scenario: account-transfer
scenarios:
account-create:
data-sources:
- details.csv
requests:
- url: http://example.com/account/create
method: POST
body:
account_owner: ${account_owner}
account_number: ${account_number}
account-transfer:
requests:
- include-scenario: account-create
- include-scenario: account-create
- url: http://example.com/transfer
method: POST
body:
account_number_from: 123
account_number_to: 321

How do I provide the correct headers for Basic Auth on a GET in OpenTest?

I'm attempting to make a GET call to a test management system that exposes an API. I want to provide Basic Auth in the header of the HTTPRequest provided as an action in OT like so.
includes: login.js
actors:
- actor: WEB
segments:
- segment: 1
actions:
- script: var xUsername = $env("X_USERNAME");
- script: var xPassword = $env("X_PASSWORD");
- script: $log("This is the username " + xUsername);
- script: $log("This is the password " + xPassword);
- description: Sample Reading testid
action: org.getopentest.actions.HttpRequest
args:
$checkpoint: true
$localData:
testRailCaseInfo: $output.body
url: https://sub.domain.io/api/v2/get_results/1234
headers:
Content-Type: application/json
Authorization: Basic xUsername xPassword
verb: GET
Is this correct?
Here are two ways to do it (please note I didn't test this code). You can either build the Authorization header value using a JavaScript expression,
- description: Read test ID
action: org.getopentest.actions.HttpRequest
args:
url: https://sub.domain.io/api/v2/get_results/1234
verb: GET
headers:
Content-Type: application/json
Authorization: |
$script
"Basic " + $base64($env("X_USERNAME") + ":" + $env("X_PASSWORD"))
or build the header value in a script action, ahead of time:
- script: |
// Build the authentication header
var authHeader =
"Basic " + $base64($env("X_USERNAME") + ":" + $env("X_PASSWORD"));
- description: Read test ID
action: org.getopentest.actions.HttpRequest
args:
url: https://sub.domain.io/api/v2/get_results/1234
verb: GET
headers:
Content-Type: application/json
Authorization: $script authHeader
I should probably explain what is the role or $script prefix in the two examples. When the value of an action argument starts with a dollar-prefixed symbol (like $json, $data, $format, etc.), the test actor understands that the expression is JavaScript code, evaluates the expression and uses the result as the value for the argument. When a JS expression doesn't start with a dollar-prefixed symbol (e.g. our expressions start with "Basic" and authHeader, respectively) we must prefix the expression with $script followed by one or more whitespace characters, to let the test actor know that the string that follows is JavaScript code and not just an ordinary string literal.
As for the format of the basic authentication scheme, you can find more information here: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization.

How to check for a certain Status Code (4xx) in Ansible?

What I want is: stop the program until status code 412 appears and then proceed.
I have the following code to check for the status code 412:
- name: Check if plan was applied
uri:
url: "https://website.some.url.com/api/v1/clusters/elasticsearch/{{elasticClusterDetails.elastic$
method: GET
user: admin
password: "{{rootpw.stdout}}"
force_basic_auth: yes
validate_certs: no
register: result
until: result.status == 412
retries: 20
delay: 30
After a few retries I get
ERROR: [...] status was 412 not 200
So the 412 actually comes up but is not recognized as fulfillment of the 'until' condition and the program exits.
From my understanding, the request can't be made when the code switches from 200 to 412.
What do I need to change to not getting an error at Code 412?
Please NOTE: This is no duplicate because checking for 4xx status code is different than to check for 2xx
Take a look at the docs for the uri module, and you'll see that there is a status_code attribute that can be used to specify one or more status codes that are considered "successful". So something like (assuming that you expect to receive either a 200 or 412 response):
- name: Check if plan was applied
uri:
url: "https://website.some.url.com/api/v1/clusters/elasticsearch/{{elasticClusterDetails.elastic$
method: GET
user: admin
password: "{{rootpw.stdout}}"
force_basic_auth: yes
validate_certs: no
status_code: [200,412]
register: result
until: result.status == 412
retries: 20
delay: 30
The problem you have right now is that the 412 status code is considered a failure.
You could also set ignore_errors: true on the task, but using the status_code attribute is probably better, because it still allows the task to failure in the event you receive unexpected status codes.
NB: The docs say, "can also be a comma-separated list of status codes", but the source looks like it expects an actual YAML list. So you may need to tweak the value depending on which value actually works.

Resources