Gatling pass feeder as a parameter to object - scala

I have a Gatling script with few objects that I use for my tests. One of the objects is used to authenticate users and users are passed to test using a feeder. The feeder is now defined on top in the beginning of the simulation class like this:
val feeder = csv("users.csv").circular
The feeder provides pairs of usernames and passwords. The object looks like:
object Auth {
val login = {
feed(feeder)
.exec(http("get_auth_token")
.post("/get_auth_token")
.body(StringBody("""{
"username": "${username}",
"password": "${password}"
}""")).asJSON
// this check extracts accessToken from JSON received in response
.check(
status.find.in(200,304,201,202,203,204,205,206,207,208,209),
jsonPath("$.response_body.access.token").saveAs("accessToken"),
jsonPath("$.response_body.refresh.token").saveAs("refreshToken")
)
).exitHereIfFailed
}
val refreshAuthToken = {
exec(http("refresh_auth_token")
.post("/refresh_auth_token")
.header("Authorization", "Bearer ${refreshToken}")
.check(
status.find.in(200,304,201,202,203,204,205,206,207,208,209),
jsonPath("$.response_body.access.token").saveAs("accessToken")
)
).exitHereIfFailed
}
}
Then I have a scenario that looks like:
val myScenario = scenario("My test")
.exec(Auth.login)
// Do some stuff here
.exec(Auth.refreshAuthToken)
// Do more stuff here
Then I have a setup that runs this single scenario.
What I would like to have is additional scenario that will run alongside with the get additional scenario that will run alongside with myScenario. This scenario will also perform Auth.login, but I want it to use a different set of users - so I need to have two separate feeders that will be passed to Auth.login from scenario. Can someone suggest how this can be achieved?

Related

How to Save a Response Body and Use It Throughout the Gatling Execution

I am using 2 API calls in my Gatling simulation. One API returns the authentication token for the second, so I need to call the token generation API call only once during the execution and use it's generated token for the second API throughout the execution. But this works only for the first cycle of execution and the token that I have saved is not getting used for the remaining executions.
object KeycloakToken extends CMS {
def request(conf: ExecutionConfig): HttpRequestBuilder = {
http("Get Auth token from Keycloak")
.post(s"${conf.authUrl}/token")
.body(StringBody(""" {
"realm": "realm",
"clientId": "clientId",
"clientSecret": "clientSecret",
"grantType": "urn:ietf:params:oauth:grant-type:token-exchange",
"userName" : "userName"
} """)).asJson
.check(jsonPath("$.access_token").saveAs("token"))
}
}
object getOffers extends CMS {
def request(conf: ExecutionConfig): HttpRequestBuilder = {
http("getOffers")
.post(s"$context")
.body(StringBody(""" Body """)).asJson
.headers(headers)
.header("Authorization", s => s"Bearer ${s.attributes("token")}")
.check(jsonPath("$.data.offers.offers[0].id").exists)
}
}
execute.apply(
scenario("service")
.exec(session => session.set("counter" , myGlobalVar.getAndIncrement))
.doIf(session => session("counter").validate[Int].map(i => i == 0)) {
exec(KeycloakToken.request(conf)) //--call only once
}
.exec(getOffers.request(conf))
)
A gatling session object is unique to each user. So when you set your counter session variable as the first step of your scenario then use a doIf to only get the token if counter==0 only the first user to execute will ever try to get the token.
Since the session is unique to that user, none of the other users will have a value for token in their session object.
What you're trying to do seems to be a pretty common issue - make a single request to get some kind of data, then have that data shared among all the users. eg: here
Note that it looks like this kind of scenario will be easier once gatling 3.4

How to set different protocol for each user (threads running) in gatling

I am trying to create a performance test suite where i have array of ID's that will be picked in random. For each ID , there is a designated auth_token assigned .
In the protocol , if i pass the method where the random ID's would take , it always sets it to that particular ID for the entire operation .
I am expecting something like , i am defining 10 virtual users and for each user the protocol should change the ID and continue the scenario execution .
Currently Gatling is setting the protocol at the first and uses the same protocol for all the 10 users.
id = random.generate //generate random id
authHeader = Method(id);
def method (id:String) : String{
if(id=="id1")
return token1
else if(id=="id2")
return token2
""
}
val httpProtocol = http.baseUrl(baseURI)
.acceptHeader(header)
.authorizationHeader(authHeader)
.contentTypeHeader(contentType)
.userAgentHeader(agentHeader)
val scn1: ScenarioBuilder = scenario("name")
.exec(http("scenario1")
.post(device_context)
.body(payload)
.check(status.is(202)))
setUp(scn1.inject(atOnceUsers(2)).protocols(httpProtocol))```
In the above code i need the suite to run for 2 different id.
To make this work, you're going to have to use a session variable to hold your authHeader. The DSL methods define builders that are only executed once - which explains what you're seeing.
The best way to do this would be to construct a feeder to hold your ids and auth tokens (I'm assuming you're not using the id anywhere else)
So define a feeder that maps a key (just a string) to a map that contains the id and auth token
//a list of strings to hold the auth tokens
private val ids = List(Map("id" -> "id1", "auth" -> "token1"), Map("id" -> "id2", "auth" -> "token2"), ...)
//the feeder to put a random token into the session
private val idFeeder = Iterator.continually(Map("id" -> Random.shuffle(ids).head))
now when you call .feed on idFeeder you get a random Map that has keys for "id" and "auth" in the session variable "id"
so update your scenario to use the feeder and set an Authorization header (and remove .authorizationHeader from your protocol definition)
val scn1: ScenarioBuilder = scenario("name")
.feed(idFeeder)
.exec(http("scenario1")
.header("Authorization", "${id.auth})"
.post(device_context)
.body(payload)
.check(status.is(202)))
in your body you can access the user id string with "${id.id}"
alternatively you could update your protocol definition to have the ${id} reference, but I find it nicer to have the feeder value used in the same block as the call to .feed

How to use a single OAuth2.0 token for Multiple Virtual Users in a Gatling load test

I need to load test an API that requires an OAuth2.0 token via Gatling (of which I'm a complete novice!) but would like each virtual user to use the same token. I'm retrieving the token ok (I think) and putting it in a variable called 'access' but I keep getting 'no attribute named 'access' is defined' when the test itself starts.
My token retrieval looks like the following(along with httpConf, used below):
class MySimulation extends Simulation {
val httpConf = http
.baseUrl("https://MyBaseUrl.Com/")
.acceptHeader("application/json")
.doNotTrackHeader("1")
.acceptLanguageHeader("en-UK,en;q=0.5")
.acceptEncodingHeader("gzip, deflate")
.userAgentHeader("Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0")
.shareConnections
val header = Map("Content-Type" -> """application/x-www-form-urlencoded""")
al auth = scenario("Retrieve Token")
.exec(http("POST OAuth Req")
.post("https://SomeTokenUrl")
.formParam("resource", "someresource")
.formParam("grant_type", "somegranttype")
.formParam("client_secret", "someclientsecret")
.formParam("client_id", "someclientid")
.headers(header).check(status.is(200)).check(jsonPath("$.access_token").find.saveAs("access")))
I then tried setting up the load test as (Note: I did initially put 'Map', rather than the mutable variant, but read somewhere the default was immutable, and wondered if this was why the header couldn't update):
val headers_10 = scala.collection.mutable.Map("Content-Type" -> "application/json; charset=ISO-8859-1", "Authorization" -> "Bearer ${access}")
val scn = scenario("MyService Gatling test run")
.exec(http("")
.post("Myservice/api")
.headers(headers_10.toMap)
.body(StringBody("""{"SomeProperty": "Some Value"}"""))
.asJson
.check(status.is(200)))
setUp(
auth.inject(constantUsersPerSec(1) during (2 seconds)),
scn.inject(nothingFor(2 seconds),
constantUsersPerSec(10) during (10 seconds)
).protocols(httpConf))
.assertions(global.responseTime.max.lt(500))
.assertions(forAll.failedRequests.percent.lte(1))
.assertions(global.responseTime.mean.lte(100))
The idea was that the token retrieval would complete prior to the load test kicking in and the 'access' variable would then be used by the load test scenario, but it gives the following result:
ERROR : Failed to build request: No attribute named 'access' is defined
I've reached the end of my tether with it. I'm guessing it might be something to do with scopes, and perhaps the variable doesn't carry over to the load test scenario, but I've seen examples elsewhere that seem to recommend exactly that set up, so I don't know whether these other examples are partially complete or what.
Today I implemented this scenario for my project. Please see the code below and it will work for you as well.
Note: I have written this code with my API's required params. You can modify this code as per your requirement. For now, this code is written in a single class. I have implemented this code in a proper format as well with the use of different classes and property files. I will post that one as well.
For a single class, the code goes as follows:
`
package aapi
import io.gatling.core.Predef._
import io.gatling.http.Predef._
class manifestSimulation extends Simulation {
private var token = ""
object authAvi{
// This is the request(API) which we are going to use for generating the auth token 1 time and then will feed this token into subsequent request.
var postBody = "{\"username\":\"devusername\”,\”password\”:\”devpassword”}”
val auth = scenario("Retrieve our auth Token which will be used in the subsequent request“)
.exec(
http("POST OAuth Req")
.post(“User Post URL“)
.body(StringBody(postBody))
.header("ClientId", “test”)
.header("DSN", “devDB”)
.header("accept", "application/json")
.header("Accept-Language", "en-us")
.header("Content-Type", "application/json")
.check(status.is(200))
.check(jsonPath("$.token")
.saveAs("token")))
.exitHereIfFailed
.exec{session => { token = session("token").as[String]
session}}
}
object manifest {
// This is the request(API) which we are going to hit multiple times using the token which we generated from the previous auth API
var manifestHeaders = Map("ClientId" -> “test”, "DSN" -> "devDB", "Token" -> "${token}")
val manifestMethod = exec(session => session.set("token", token))
.exec(http("Manifest Details")
.get(“Your get URL“)
.headers(manifestHeaders)
.check(status.is(200))
)
}
val scn = scenario(“**********This is your actual load test*******************”)
.exec(manifest.manifestMethod)
setUp(
authAvi.auth.inject(constantUsersPerSec(1) during (1 seconds)), // fire 1 requests per second for 1 second to retrieve token
scn.inject(nothingFor(4 seconds), // waits 4 seconds as a margin to process token and this time varies for every user
constantUsersPerSec(5) during (5 seconds))) // fire 5 requests per second for 5 seconds which will result in 25 (5*5) requests and overall 26 requests when the report gets generated (because we have 1 request for auth token and 25 requests of our intended API (25+1 = 26)
}`
Sessions are per user and no session data is shared between users. So while you have 1 user running your 'auth' scenario and saving the token, it is two different users that run 'scn' and they don't have access to the session values of the auth user.
It's not recommended practice, but you can solve this by pushing the auth token into a regular scala var and the setting this in the auth scenario and reading it in the main scenario - you just need to be sure that auth always completes before you inject any other users.
var token: String = ""
then in the auth scenario, have a step at the end such as
.exec(session => {
token = session("access").as[String]
session
})
then at the start of the scn scenario have a step to set the session variable
.exec(session.set("access", token))
I've used this pattern in the past and it works, but I'm sure there are nicer ways to do it
#Tarun,
When I did it, I had the 'exec' in my scenario, rather than the set up, and used the following syntax:
val dataToUse = feed(testData)
.exec(session => session.set("access", token))
.exec(http("")
.post("*the_URL_to_send_to)*")
.headers(headers_10.toMap)
.body(RawFileBody("${filePath}")).asJson
.check(status.is(200))
)
As mentioned in the comments in the previous discussion, this was because I was using a later version of gatling and the 'get' method was no longer part of the session api.
My full solution was as follows - note that are a number of things to look out for that might not apply to your solution. I used an object, as it just made things clearer in my mind for what I was trying to do! Also, some of the imports are probably redundant, as I included them as part of scattergun approach to finding something that worked!
Finally, I basically list the contents of a directory, and cycle through the files listed in it, using each one as a feeder. You look as if you're using a literal template of json, so probably don't need that, but I thought I would include it for completeness, as it's quite handy - if you change the format of your json, you don't need to mess around changing the template in the simulation, you just clear the directory and drop examples of the new format in there and away you go! :
package myTest
import io.gatling.core.Predef._
import io.gatling.http.Predef._
import scala.concurrent.duration._
import scala.collection.JavaConversions._
import java.io.File
import java.io.FileNotFoundException
class myTestSimulation extends Simulation {
val httpConf = http
.baseUrl("*your_base_URL*")
.acceptHeader("application/json") // Here are the common headers
.doNotTrackHeader("1")
.acceptLanguageHeader("en-UK,en;q=0.5")
.acceptEncodingHeader("gzip, deflate")
.userAgentHeader("Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0")
.shareConnections
val header = Map("Content-Type" -> """application/x-www-form-urlencoded""");
private var token = ""
val auth = scenario("Retrieve Token")
.exec(
http("POST OAuth Req")
.post("*URL_for_Token*")
.formParam("resource", "*your_resource_value*")
.formParam("grant_type", "*your_grant_type*")
.formParam("client_secret", "*your_client_secret_value*")
.formParam("client_id", "*your_client_id_value*")
.headers(header)
.check(status.is(200)).check(jsonPath("$.access_token").find.saveAs("access")))
.exec{session => { token = session("access").as[String]
session}}
object myTestObject {
var headers_10 = scala.collection.mutable.Map("Content-Type" -> "application/json; charset=ISO-8859-1", "Authorization" -> "Bearer ${access}")
val testData = Iterator.continually(
new File("*pathway_to_file*") match {
case d if d.isDirectory => d.listFiles.map(f => Map("filePath" -> f.getPath))
case _ => throw new FileNotFoundException("Samples path must point to directory")
}).flatten
val myTestObjectMethod = feed(testData)
.exec(session => session.set("access", token))
.exec(http("")
.post("*the_URL_to_send_to(don't_forget_that_base_URL_above_is_automatically_stuck_to_the_front_of_this!)*")
.headers(headers_10.toMap)
.body(RawFileBody("${filePath}")).asJson
.check(status.is(200))
)
}
val scn = scenario("my_actual_load_test")
.exec(myTestSimulation.myTestObject)
setUp(
auth.inject(constantUsersPerSec(1) during (1 seconds)), // fire 1 requests per second for 1 second to retrieve token
scn.inject(nothingFor(2 seconds), // waits 2 seconds as a margin to process token
constantUsersPerSec(50) during (300 seconds) // fire 50 requests per second for 300 seconds
).protocols(httpConf))
.assertions(global.responseTime.max.lt(500)) // set max acceptable response time
.assertions(forAll.failedRequests.percent.lte(1)) // less than 1% of tests should fail
.assertions(global.responseTime.mean.lte(100)) // set average response time
}
I mean, I've probably made a typo somewhere along the line as I removed the sensitive stuff from it, but hopefully that will do what you need.

How to access a value stored in a session from within a feeder in Gatling

In my Gatling scenario, a value is stored in the session for a user.
Later in the same scenario, feed is called and passed a custom feeder. The custom feeder needs to generate its next value using the stored value in the session.
val MyScenario = scenario("ScenerioName")
.repeat(10, "repetition") {
exitBlockOnFail {
group("WorkflowGroupName") {
exec(session => {
// SETTING A VALUE INTO THE USER'S SESSION
session.set("sessionVariable", 99) // value that is stored changes for every run of the workflow (99 just for example purposes)
})
// CUSTOM FEEDER THAT GENERATES ITS NEXT VALUE USING THE SESSION VARIABLE 'sessionVariable' STORED ABOVE
.feed(myFeeder)
.group("RequestGroup1") {
exec(httpPost1)
}
}
}
}
val myFeeder = Iterator.continually(Map("jsonFileValue" -> {
// WANT TO RETRIEVE VALUE OF 'sessionVariable' STORED IN THE SESSION
val returnValue = /* logic that generates its value based on value of 'sessionVariable' retrieved */
returnValue
}
))
val httpPost1 = http("Request1")
.post("http://IPAddress/service.svc")
.headers(httpHeaders)
.body(ELFileBody("MyJsonFile.json"))
.check(status.is(200))
val httpHeaders = Map(
"Content-Type" -> "application/json; charset=UTF-8",
"X-Pod" -> ""
)
How can I pass this stored session value to the feeder or have the feeder retrieve this value from the session?
As the documentation states:
Sometimes, you could want to filter the injected data depending on
some information from the Session.
Feeder can’t achieve this as it’s just an Iterator, so it’s unaware of
the context.
If your values are independent of the tests you run, maybe a good way can be to generate a csv before you run your tests and then feed this csv to your tests.

Gatling 2 dynamic queryParam on each request

I am trying to run a load test using Gatling 2. I need to generate one of the query parameters dynamically on each request.
My scenario is defined like this:
val scn = scenario("Load Test Scenario")
.exec(
http("Test API")
.post(url)
.body(StringBody("Some XML"))
.queryParam("x", DigestUtils.md5Hex(generateX().getBytes("UTF-8")))
)
def generateX() : String = {
// generate random string and return
}
This only calls generateX once and uses the result in each request. Is there anyway to have the generateX call on every request?
You have to pass a function, not a value. See Gatling documentation about Expression.
Here, you can just discard the session input parameter as you don't use it, so you can simply write:
.queryParam("x", _ => DigestUtils.md5Hex(generateX().getBytes("UTF-8")))