Retrieve Session attribute values to pass in to function

Hi,

I’m trying to use EL expressions and pass a value from a feeder into a function, but the EL expression is not being evaluated and instead the string is being passed in “as is” (e.g. “${volumeId}”). I’m familiar with the type Session => Validation[T], but I can’t figure out how to make it work in this case.

Here’s my code.

`
val volumeFeeder = csv(“volumes.csv”)
val usersFeeder = csv(“users.csv”).random
val serversFeeder = csv(“servers.csv”)

val scn = scenario(“Create, Boot From Volume, Delete Server”)
.exec(Identity.auth(usersFeeder, Options.OS_REGION_NAME ))
.feed(volumeFeeder)
.feed(serversFeeder)
.exec(
Volumes.createBootVolumeFromImage,
Volumes.waitForVolume("${volumeId}", “available”),
Servers.bootFromVolume("${serverName}", “${flavorId}”, “${volumeId}”),
Servers.waitForServer("${serverId}", “ACTIVE”),
Servers.delete("${serverId}"),
Volumes.delete("${volumeId}"))
`

The feeders contain the appropriate values for these EL expressions.

How can I retrieve the values out of the session, so I can pass them in to these functions?

Thanks in advance!

You can only pass EL strings into Gatling functions; it doesn’t work with regular Scala code: https://groups.google.com/d/msg/gatling/DLQJyT0j4yg/fMsYexjlQmEJ

Right, I understand they are just plain Strings. Probably I’m missing some obvious way. But, what I’m trying is to find a way that I can extract the values out of the session, and pass them in to my functions.

Perhaps something like (which seems verbose):

.... .exec( Volumes.createBootVolumeFromImage(session("volumeId").as[String], "available"), Volumes.waitForVolume(session("serverName").as[String], session("flavorId").as[String], session("volumeId").as[String]), ... )

or, leaving they function calls just like in my first post but re-defining my functions to take Expression[String] parameters, but then the problem with that, is that I don’t know how to pass in the session object to evaluate the function Session => Validation[String]

My problem basically boils down to, how do I get access to the Session object?

Thanks

You need to use a session function:

.exec(session => {
Volumes.createBootVolumeFromImage(session(“volumeId”).as[String], “available”),
Volumes.waitForVolume(session(“serverName”).as[String], session(“flavorId”).as[String], session(“volumeId”).as[String]),

session
})

(Make sure you return a Session object as the last statement.)

Hi Carlos,

How do your volumeId is used in your functions to create the requests ?
Basically, what you need is to get to a point where Gatling gives you the session and allows to access it.
One example is StringBody, which takes a Expression[String].
get(…), post(…) also gives you acces to the session.

When you identified the point where you need the values from the session and where Gatling gives you access to the session, the problem is pretty simple to solve : take advantage of Scala and that functions are first-class citizens and pass around a function that takes a Session and gives you what you need.

For example, let’s say that Volumes.createBootVolumeFromImage use the volumeId in a StringBody.
You would define Volumes.createBootVolumeFromImage that way :

def createBootVolumeFromImage(volumeId: Expression[String], attribute: String) =
http(“createBootVolueFromImage”)
.get("/myurl")
.body(StringBody(session => volumeId(session)))

And use it that way :

.exec(Volumes.createBootVolumeFromImage(session => session(“volumeId”).as[String], "available’)

Hope this helps !

Cheers,

Pierre

Hi Pierre,

That looks promising! Before I try it, here’s what I was trying to do with the strings, and the rationale behind it.

`
def bootFromVolume(name: String, flavorId: String, volumeId: String) =
exec { session =>
session
.set(“serverName”, name)
.set(“flavorId”, flavorId)
.set(“volumeId”, volumeId)
}
.exec(http(“server_boot_volume”)
.post("${computeURL}/os-volumes_boot")
.header(“X-Auth-Token”, “${authToken}”)
.body(ELFileBody(“boot_volume.json”))
.check(status.is(202))
.check(jsonPath("$.server.id").saveAs(“serverId”)))

`

ELFileBody uses these values.

Then I was calling it as follows:

.feed(volumeFeeder) .exec(... Servers.bootFromVolume("${serverName}", "${flavorId}", "${volumeId}"), ...)

I know I can just omit the parameters, and refer to them directly since they’ve already been fed into the session by the feeder (for this use case), but I was striving to be more explicit with the values being “passed in” for readability’s sake. I want to make it explicit that these functions require these parameters (except for computeURL, authToken), so other “users” could immediately know how to use them, instead of requiring them to inspect the function code to understand what session variables it expects. Also, the data might not always be already set in the session, so the function parameters can be used to pass in constant values.

It seems that with your last suggestion, I might be able to achieve this.

Hi Pierre,

I tried your suggestion, but because the way I structured my code the approach, at least for me, yields an unnecessary indirection. I was pretty much setting the values in the session with the feeder, then using EL expressions to pass them into the function, and then again, evaluating them against the session to set the ‘same’ values in the session!

So, I ended up removing the parameters from the functions, and passing these values implicitly through the session. I guess it’s implied that input data comes from the feeders, but I feel this is not clean enough, since it would require to look at both at the feeder data, and the functions (or even the ELFiles) to understand how data flows through. For example,

`
.feed(volumesFeeder)
.exec(
Volumes.createBootVolumeFromImage,
Volumes.waitForVolume(“available”))
.feed(serversFeeder)
.exec(
Servers.bootFromVolume,
Servers.waitForServer(“ACTIVE”),
Servers.delete,
Servers.waitForServer(“DELETED”),
Volumes.delete)

`

and one of the functions looks like this:

def bootFromVolume = exec(http("server_boot_volume") .post("${computeURL}/os-volumes_boot") .header("X-Auth-Token", "${authToken}") .body(ELFileBody("boot_volume.json")) .check(status.is(202)) .check(jsonPath("$.server.id").saveAs("serverId")))

Ideally, I wish I could independently create methods that return these ChainBuilders objects that are understood just from their arguments, without relying on the session for parametrization. I feel the session behave like a global variable, and impairs readability . But I guess my lack of Scala experience is giving me a hard time to understand how I can achieve this.

I guess for my use case this is good enough, since it involves a small number of functions. But, I don’t want to take more of your time, though I’m open to suggestions and ideas. Perhaps, some have already discovered patterns or idiomatic ways to structure the code in such way to increase composition, reuse and readability, akin to PageObjects in Selenium, which in a sense I was striving for.

Thanks! By the way awesome work with Gatling, There’s nothing out there that can compete in terms of flexibility, ease of use, expressive power, and performance. Great job guys!

– Carlos

Hi Carlos,

It’ll seems weird to say it but, in the way, the “problem” here is that you’re using ELFileBody which takes care of resolving values from the Session on its own.
There is absolutely nothing wrong with ELFileBody, on the contrary. But in the case of ELFileBody, doing what I suggested indeed add an unwanted and unneeded indirection.
This would be completely different if you relied on StringBody, combined with Scala’s String interpolation/Fastring. In that case you could need and want to have a strongly-typed template-based system, where session lambdas could prove very useful.
To show you an example, I’ll take an example from a set of simulations I’m working on :

I’m building load tests for a set of SOAP-based web services.
For some of those web-services, there are parameters that are optional, and ELFileBody is not powerful to handle that, so we need the full power of a programming language, and went on a Fastring-based template system, to factor out and simplify the use of the request.

Every request relies on the string template of the request and class holding all the request parameters.

So we end up with something like it :

`
object ASoapCallTemplate {

case class Params(userId: Int, username: String, password: String)

def template(params: Params): String = fast"""…"""
}
`

To build request, we also followed a common pattern for every template :

def request(params: Expression[Params]) = http("...") .post("/myurl") .body(StringBody(session => template(params(session))))

And in a case like that, using a session function, through the use of an Expression[Params], make sense, as it still allows to fetch data from the session, but ensure that the types are correct (to a limited extent, as we need to cast data from the Session to the required type using as, but it’s better than nothing ;)) :

val buildSoapCallParams: Expression[Params] = (session: Session) => Params( session("userId").as[Int], session("username").as[String], session("password").as[String]).success

Of course, not everybody need that level of flexibility.
But that’s a case where passing around session functions is really helpful.

And thanks for your kind words :wink:

Cheers,

Pierre

You are right. ELFileBody was twisting my arm into going through the unnecessary indirection. I can definitely see how this is much cleaner, flexible and type safe than what I was doing before. Plus I like how it packs all params into a single object instead of passing each individually, and the templating logic is separate from the HTTP request

For my particular scenario this might be overkill, but I will definitely have it mind next time.

That example should be in the Advance section of the docs. :slight_smile:

Quick question, is this Fastring a separate library?

Thanks again,
Carlos

Indeed, even if it can provide a common structure for all your requests, this takes some time to setup, and might not be worthwhile if your scenarios stay “simple” enough.

Will keep that idea in mind, it could be nice to have a somewhat complex example of a mini template-based system in the documentation.

Fastring is a separate library but, as Gatling make heavy use of it (it’s the base of our charts templating system), it’s in the classpath and available for use in your simulation without having to pull it yourself.

You’re welcome :slight_smile:

Cheers,

Pierre

Sorry for reviving this old thread.

I’m currently implementing something similar to your request templates, where some of the values in the JSON body are optional and I was curious how do you exactly handle the optional parameters in your case? More case classes? or do you use default values? or what?

To give you some context, here’s what I’m trying to achieve using your previous example. I would like to have something as follows:

`
def request(params: Expression[Params]) =
http("…")
.post(“myurl”)
.body(StringBody(session => template(params(session))

request(Params(
session(“username”),
session(“password”),
))

request(Params(
session(“username”),
session(“apitoken”)
))

`

where “password” or “apitoken” are optional, but at least one of them has to be specified

Thanks
Carlos Torres

Can you build your JSON object as a Map, injecting only the key/value pairs that need to be the final object, and then convert the final Map to a JSON string all at once?

Basic concept:

def passwordSpecificPartTemplate(password: String): String = s"foo$passwordbar"
def apitokenSpecificPartTemplate(apitoken: String): String = s"foo$apitoken"

val template = (session: Session) =>
s"""
JSON stuff
${
session(“password”).asOption[String] match {
case Some(password) => passwordSpecificPartTemplate(password)
case None => session(“apitoken”).asOption[String] match {
case Some(apitoken) => apitokenSpecificPartTemplate(apitoken)
case None => “”
}
}
}
some other JSON stuff
“”"

This implementation has several drawbacks:

While, like this, I have access to the session entries, e.g. both entries will not be executed. I have a similar construct and I want to perform an HTTP call within this session function, but it never gets executed. Any idea why and/or how to solve this? My underlaying problem why I need to access via session(“x”).as[x] rather then “${x}” is that I am not saving a String in the Session, but a List[Map[String, String]] and I want to pass it to a method, so the signature does not match (String vs. List).

Does anyone have an idea?