3
votes

Comment utiliser un seul jeton OAuth2.0 pour plusieurs utilisateurs virtuels dans un test de charge Gatling

J'ai besoin de tester une API qui nécessite un token OAuth2.0 via Gatling (dont je suis un novice complet!) mais j'aimerais que chaque utilisateur virtuel utilise le même token. Je récupère le jeton ok (je pense) et le mets dans une variable appelée 'accès' mais je continue à obtenir 'aucun attribut nommé' accès 'n'est défini' lorsque le test lui-même commence.

Ma récupération de jeton ressemble au suivant (avec httpConf, utilisé ci-dessous):

  ERROR : Failed to build request: No attribute named 'access' is defined 

J'ai ensuite essayé de configurer le test de charge comme (Remarque: j'ai d'abord mis 'Map', plutôt que la variante mutable , mais lu quelque part, la valeur par défaut était immuable, et je me suis demandé si c'était la raison pour laquelle l'en-tête ne pouvait pas être mis à jour):

 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)) 

L'idée était que la récupération du jeton se terminerait avant le démarrage du test de charge et la variable 'access' serait alors utilisée par le scénario de test de charge, mais cela donne le résultat suivant:

 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"))) 

J'ai atteint la fin de mon attacher avec elle. Je suppose que cela pourrait être quelque chose à voir avec les étendues, et peut-être que la variable ne se reporte pas au scénario de test de charge, mais j'ai vu des exemples ailleurs qui semblent recommander exactement cette configuration, donc je ne sais pas si ces autres exemples sont partiellement complets ou quoi.


0 commentaires

4 Réponses :


1
votes

Les sessions sont par utilisateur et aucune donnée de session n'est partagée entre les utilisateurs. Ainsi, alors que vous avez 1 utilisateur exécutant votre scénario 'auth' et enregistrant le jeton, ce sont deux utilisateurs différents qui exécutent 'scn' et ils n'ont pas accès aux valeurs de session de l'utilisateur auth.

Ce n'est pas une pratique recommandée , mais vous pouvez résoudre ce problème en poussant le jeton d'authentification dans un var scala normal et en le définissant dans le scénario d'authentification et en le lisant dans le scénario principal - il vous suffit de vous assurer que l'authentification se termine toujours avant d'injecter d'autres utilisateurs.

.exec(session.set("access", token))

puis dans le scénario auth, avoir une étape à la fin comme

.exec(session => {
    token = session("access").as[String]
    session
})

puis au début du scn scénario a une étape pour définir la variable de session

var token: String = ""

J'ai utilisé ce modèle dans le passé et cela fonctionne, mais je suis sûr qu'il existe de meilleures façons de le faire


2 commentaires

Merci pour votre réponse James, mais je n'arrive toujours pas à la faire fonctionner. Mon problème est le bit 'token = session.get ("access"). As [String]'. Il ne reconnaît pas «obtenir». J'ai ensuite essayé '.exec (session => {token = session ("access")})', qui a naturellement donné une incompatibilité de type car le jeton était une chaîne, et il retournait un type SessionAttribute. J'ai ensuite relu le '.as [String]' jusqu'à la fin, et j'obtiens maintenant une incompatibilité de type différente - cela indique maintenant qu'il a trouvé le type 'unit', mais attendait 'Validation'?!?!


Salut James - a trouvé le problème. 'get' ne fait plus partie de l'API de session dans les versions ultérieures de Gatling, qui est la version que j'utilise (en plus, je pense qu'il fallait que ce soit des accolades plutôt que des normales autour de la session!). Si vous souhaitez modifier votre réponse pour mentionner que vous devrez peut-être avoir .exec {session => {token = session ("access"). As [String] session}} si vous êtes sur une version ultérieure de Gatling, je la marquerai comme la bonne réponse. Merci de votre aide!



0
votes

J'essaie également ce scénario, mais j'obtiens une erreur lorsque j'essaie d'ajouter .exec {session => {token = session ("access"). as [String] session}} au début du scénario scn .

  setUp(auth.inject(constantUsersPerSec(1) during (2 seconds)),
  .exec{session => { token = session("access").as[String] session}}
  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)) 

J'ai également essayé séparément les modifications ci-dessous: -

       val scn = scenario("MyService Gatling test run")
       .exec{session => { token = session("access").as[String] session}}  
      .exec(http("")               
       .post("Myservice/api")
       .headers(headers_10.toMap)                
       .body(StringBody("""{"SomeProperty": "Some Value"}"""))
       .asJson
       .check(status.is(200)))

mais aucune d'elles n'a fonctionné. Pouvez-vous me suggérer où dois-je mettre ce code.


1 commentaires

Salut Tarun - voir la réponse ci-dessous. J'espère que cela couvrira ce que vous recherchiez.



0
votes

@Tarun,

Quand je l'ai fait, j'avais le 'exec' dans mon scénario, plutôt que la configuration, et j'ai utilisé la syntaxe suivante:

 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
 }

Comme mentionné dans les commentaires de la discussion précédente, c'était parce que j'utilisais une version ultérieure de gatling et que la méthode «get» ne faisait plus partie de l'API de session.

Ma solution complète était la suivante - notez qu'il y a un certain nombre de choses à surveiller qui pourraient ne pas s'appliquer à votre solution. J'ai utilisé un objet, car cela rendait les choses plus claires dans mon esprit pour ce que j'essayais de faire! De plus, certaines importations sont probablement redondantes, car je les ai incluses dans le cadre de l'approche scattergun pour trouver quelque chose qui a fonctionné!

Enfin, je répertorie essentiellement le contenu d'un répertoire et je parcours les fichiers répertoriés dans il, en utilisant chacun comme un chargeur. Vous avez l'air d'utiliser un modèle littéral de json, donc probablement pas besoin de cela, mais j'ai pensé que je l'inclurais pour être complet, car c'est assez pratique - si vous changez le format de votre json, vous ne le faites pas besoin de changer le modèle dans la simulation, il vous suffit d'effacer le répertoire et de déposer des exemples du nouveau format là-dedans et c'est parti! :

 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))
             )

Je veux dire, j'ai probablement fait une faute de frappe quelque part le long de la ligne en supprimant les éléments sensibles, mais j'espère que cela fera ce dont vous avez besoin. p>


0 commentaires

2
votes

Aujourd'hui, j'ai implémenté ce scénario pour mon projet. Veuillez consulter le code ci-dessous et il fonctionnera également pour vous.

Remarque: j'ai écrit ce code avec les paramètres requis de mon API. Vous pouvez modifier ce code selon vos besoins. Pour l'instant, ce code est écrit dans une seule classe. J'ai également implémenté ce code dans un format approprié avec l'utilisation de différentes classes et fichiers de propriétés. Je publierai celui-là aussi.

Pour une seule classe, le code se présente comme suit:

`

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)

     }`


0 commentaires