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

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.

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

Why won't gatling post an auth token from a POST return body to a GET header

I tried all the suggestions I found from other examples on this site and I still cannot get my Gatling test to pass the authentication token from a POST to the following GETs. The token is taken from the return body of the POST and passed in as a header to the GET
Initially the login was in BaseSimulationTest but I put it in GetDealsTests for troubleshooting
Steps I have tried:
I added this:
//println(" Token value" + session("accessToken").as[String])
And I was able to see that I got a string back in the terminal that seemed to indicate accessToken was a null value
I tried declaring var accessValue and var accessToken both in the method and globally. No change.
I tried checking the header value on the GET after passing the token in but .check(header.is(expected = ${accessToken})) seems to just error out
I have put the login POST and GET in the same method, different methods etc
I have tried passing in the username and password from .formParam instead of in the body of the request statement
Do I need to do the headers as a map? Is this a scope thing? Do I need to declare variables differently? The tests are running with an "expected 200 but received 401" type result. I think the POST doesn't even see the token being passed to it.
class GetDealsTests extends BaseSimulationTest {
def logIn2() = {
exec(http("Log in")
.post(getUrl() + "login")
.header("Content-Type", "application/json")
.body(ElFileBody("bodies/Login.json")).asJson
.check(status.is(200))
.check(jsonPath("$.access_token").saveAs("accessToken")))
exec(
session=>{
accessValue = session("accessToken").as[String]
session
})
}
def getAllDeals() = {
exec(_.set("accessToken", accessValue))
.exec(
http("Get all deals")
.get("deals/all")
.header("Authorization", "Bearer" + "${accessToken}")
.check(status.is(200))
)
}
val scnGetAllDeals = scenario("Get All Deals endpoint tests")
.forever() {
exec(logIn2())
exec(getAllDeals())
}
setUp(
scnGetAllDeals.inject(
nothingFor(5 seconds),
atOnceUsers(users))
).protocols(httpConf.inferHtmlResources())
.maxDuration(FiniteDuration(duration.toLong, MINUTES))
}
I looked at the following: Gatling won't save access token, Gatling Scala:Unable to send auth token to the method using session variable, Gatling - Setting Authorization header as part of request and don't see what I'm doing wrong
Lots of things:
Both logIn2 and getAllDeals are missing dots to properly chain the different method calls. As an example, here's what your logIn2 is actually doing (properly formatting helps):
def logIn2() = {
val discardedResult = exec(http("Log in")
.post(getUrl() + "login")
.header("Content-Type", "application/json")
.body(ElFileBody("bodies/Login.json")).asJson
.check(status.is(200))
.check(jsonPath("$.access_token").saveAs("accessToken")))
return exec(
session => {
accessValue = session("accessToken").as[String]
session
})
}
You probably shouldn't be using a global var. You don't need it and such construct is not threadsafe, so under load, virtual users will be updating and reading from this reference concurrently and result will be a mess.
You're missing a space in your Authorization header, between Bearer and the actual value.
asJson is merely a shortcut for .header("Content-Type", "application/json"), so you setting this header twice.
class GetDealsTests extends BaseSimulationTest {
val logIn2 =
exec(http("Log in")
.post(getUrl() + "login")
.body(ElFileBody("bodies/Login.json")).asJson
.check(status.is(200))
.check(jsonPath("$.access_token").saveAs("accessToken")))
val getAllDeals =
exec(
http("Get all deals")
.get("deals/all")
.header("Authorization", "Bearer ${accessToken}")
.check(status.is(200))
)
val scnGetAllDeals = scenario("Get All Deals endpoint tests")
.forever {
exec(logIn2)
.exec(getAllDeals)
}
setUp(
scnGetAllDeals.inject(
nothingFor(5 seconds),
atOnceUsers(users))
).protocols(httpConf.inferHtmlResources())
.maxDuration(FiniteDuration(duration.toLong, MINUTES))
}

Stub a Http4s 0.20.x Client for testing

I'd like to test that a Http4s Client is being called from my class ClassUnderTest (to make a HTTP request) and that the request made contains the headers I expect.
In 0.18.x, I did something like below. Using a side effect to store the value of the headers so that I can make an assertion after the call. In the example below, f.execute(...) is expected to make a PUT with the client instance and I'm trying to record all request handling and storing the headers.
"Request has headers" >> {
var headers = List[String]()
val client = Client[IO](new Kleisli[IO, Request[IO], DisposableResponse[IO]](request => {
headers = request.headers.map(_.name.toString()).toList
Ok().map(DisposableResponse(_, IO.pure(())))
}), IO.pure(()))
val f = ClassUnderTest(client)
f.execute("example")
headers must_== List(
"Content-Type",
"X-Forwarded-For",
"Content-Length"
)
}
The real code is here if you're interested.
ClassUnderTest took a Client[IO] so I could get the above working.
class ClassUnderTest(client: http4s.Client[IO])
In Http4s 0.20.12, I had to change the signature to:
class ClassUnderTest(client: Resource[IO, http4s.Client[IO]])
...and now I can't figure out how to stub out the client for tests. I experimented with JavaNetClientBuilder but that doesn't help because I can get an instance of Client (after .create) and now I need a Resource[IO, http4s.Client[IO]].
How can I use a test double to stand in for the Client / Resource[F, Client[F]] so that I can test the requests it makes?
The testing page on the docs doesn't really help me. I want a test double, not to test all the functionality of the Service (I don't want to startup a server).

Not able to extract values in Gatling

I have written 2 sets of Gatling code. My usecase in example 1 is make a post call. extract a value. make a get call and use the extracted value in header.
My usecase in example 2 is make a post call, extract a value, make a get call use the extracted value in cookie.
Example 1
val login = http("Login")
.post("login")
.body(StringBody("""{"username": "foo", "password": "bar"}""")).asJSON
.check(status.is(200), jsonPath("$..response.id").ofType[String].saveAs("id"))
val get = http("get")
.get("foo")
.header("token", "$id")
.check(status.is(200), jsonPath("$..response").exists)
var id = ""
val scn = scenario("scenario")
.exec(login)
.exec(session => {
id = session("id").as[String].trim
println("+++++++" + id)
session}
)
.pause(3)
.exec(get)
When I run this code I see that the print statement above prints the correct ID. The server throws 403 on the get call because the ID is not being set correctly. If I take any of the printed values and then remote $id and replace it with that. the test runs correctly.
So how do I access the saved variable?
Example 2
val login = http("Login")
.post("login")
.body(StringBody("""{"username": "foo", "password": "bar"}""")).asJSON
.check(status.is(200), jsonPath("$..response.id").ofType[String].saveAs("id"))
val get = http("get")
.get("foo")
.check(status.is(200), jsonPath("$..response").exists)
val testCookie = scenario("test-cookie")
.exec(login)
.pause(3)
.exec(addCookie(Cookie("foo_cookie", "$id")))
.exec(get)
Here also the value is not extracted successfully and I get a 403 when I run it because the "foo_cookie" was not set correctly and the server will throw a 403 if the cookie is not found. (in postman I can make the same call work by specifying the cookie correctly)
I was able to resolve the issue. The problem was this line
.header("token", s"$id")
In Scala $id and ${id} are same but it appears that in Gatling they are not.
when I replaced my code to
.header("token", "${id}")
note that there is no "s" behind the string.
it worked!

How to remove cookies on Finatra?

How do I remove a cookie after processing the request and building the response?
I have tried the following code, but it does not seem to work:
get("/login") { request =>
val message = request.cookies.get("flash-message").map(_.value)
request.removeCookie("flash-message")
render.view(LoginView(message)).toFuture
}
I could not find any methods on ResponseBuilder that would remove a cookie, either.
It turns out, the way to do it, is the usual "JavaScript" way. Just create an expired cookie and send back, like this:
import com.twitter.finagle.http.Cookie
import com.twitter.util.Duration
import java.util.concurrent.TimeUnit
get("/login") { request =>
val message = request.cookies.get("flash-message").map(_.value)
val c = Cookie("flash-message", "")
c.maxAge = Duration(-10, TimeUnit.DAYS)
render.view(LoginView(message)).cookie(c).toFuture
}
Of course 10 days is just an arbitrary "duration" in the past.