2016-06-16 45 views
26

Risolto: Grazie a below answer da S.Richmond. Ho dovuto disinserire tutte le mappe memorizzate del tipo groovy.json.internal.LazyMap che significava annullare le variabili envServers e object dopo l'uso.Jenkins Pipeline NotSerializableException: groovy.json.internal.LazyMap

supplementari: Le persone alla ricerca di questo errore potrebbero essere interessati a utilizzare il gasdotto passo Jenkins readJSON invece - trovare maggiori informazioni here.


Sto cercando di utilizzare Jenkins Pipeline per prendere input dall'utente che viene passato al lavoro come stringa json. La pipeline quindi analizza questo usando il fluidificatore e seleziono le informazioni importanti. Quindi utilizzerà tali informazioni per eseguire 1 lavoro più volte in parallelo con i parametri di lavoro differeing.

Fino a quando non aggiungo il codice sotto "## Error when below here is added" lo script funzionerà correttamente. Anche il codice sotto quel punto funzionerà da solo. Ma quando combinato ho l'errore qui sotto.

Si noti che il lavoro attivato viene chiamato e viene eseguito correttamente, ma si verifica l'errore di sotto e il lavoro principale non viene eseguito correttamente. Per questo motivo, il lavoro principale non attende il ritorno del lavoro attivato. I potrebbe tentare/aggirare lo build job: tuttavia, desidero che il lavoro principale attenda il completamento del processo attivato.

Qualcuno può assistere qui? Se hai bisogno di ulteriori informazioni fammi sapere.

Acclamazioni

def slurpJSON() { 
return new groovy.json.JsonSlurper().parseText(BUILD_CHOICES); 
} 

node { 
    stage 'Prepare'; 
    echo 'Loading choices as build properties'; 
    def object = slurpJSON(); 

    def serverChoices = []; 
    def serverChoicesStr = ''; 

    for (env in object) { 
    envName = env.name; 
    envServers = env.servers; 

    for (server in envServers) { 
     if (server.Select) { 
      serverChoicesStr += server.Server; 
      serverChoicesStr += ','; 
     } 
    } 
    } 
    serverChoicesStr = serverChoicesStr[0..-2]; 

    println("Server choices: " + serverChoicesStr); 

    ## Error when below here is added 

    stage 'Jobs' 
    build job: 'Dummy Start App', parameters: [[$class: 'StringParameterValue', name: 'SERVER_NAME', value: 'TestServer'], [$class: 'StringParameterValue', name: 'SERVER_DOMAIN', value: 'domain.uk'], [$class: 'StringParameterValue', name: 'APP', value: 'application1']] 

} 

Errore:

java.io.NotSerializableException: groovy.json.internal.LazyMap 
    at org.jboss.marshalling.river.RiverMarshaller.doWriteObject(RiverMarshaller.java:860) 
    at org.jboss.marshalling.river.RiverMarshaller.doWriteObject(RiverMarshaller.java:569) 
    at org.jboss.marshalling.river.BlockMarshaller.doWriteObject(BlockMarshaller.java:65) 
    at org.jboss.marshalling.river.BlockMarshaller.writeObject(BlockMarshaller.java:56) 
    at org.jboss.marshalling.MarshallerObjectOutputStream.writeObjectOverride(MarshallerObjectOutputStream.java:50) 
    at org.jboss.marshalling.river.RiverObjectOutputStream.writeObjectOverride(RiverObjectOutputStream.java:179) 
    at java.io.ObjectOutputStream.writeObject(Unknown Source) 
    at java.util.LinkedHashMap.internalWriteEntries(Unknown Source) 
    at java.util.HashMap.writeObject(Unknown Source) 
... 
... 
Caused by: an exception which occurred: 
    in field delegate 
    in field closures 
    in object [email protected] 
+0

appena incontrato questo me stesso. Hai fatto ulteriori progressi ancora? –

+2

https://github.com/jenkinsci/pipeline-plugin/blob/master/TUTORIAL.md#serializing-local-variables –

risposta

21

mi sono imbattuto in questo io stesso oggi e attraverso alcune bruteforce ho capito sia come risolverlo e potenzialmente perché.

Probabilmente migliore per iniziare con il perché:

Jenkins ha un paradigma in cui tutti i processi possono essere interrotti, si fermò e ripristinabile tramite riavvio del server. Per raggiungere questo obiettivo, la pipeline ei suoi dati devono essere completamente serializzabili, cioè deve essere in grado di salvare lo stato di tutto. Allo stesso modo, deve essere in grado di serializzare lo stato delle variabili globali tra nodi e sotto-lavori nel build, che è ciò che penso stia accadendo per te e me e perché si verifica solo se aggiungi quel passo aggiuntivo.

Per qualsiasi motivo, JSONObject non è serializzabile per impostazione predefinita. Non sono uno sviluppatore Java quindi non posso dire molto di più sull'argomento. Ci sono un sacco di risposte là fuori su come si può risolvere questo correttamente anche se non so quanto siano applicabili a Groovy e Jenkins. See this post per un po 'più di informazioni.

Come a risolvere il problema:

Se si sa come, si può eventualmente fare la JSONObject serializzabile in qualche modo. Altrimenti puoi risolverlo assicurandoti che nessuna variabile globale sia di quel tipo.

Provare a disattivare il valore object var oa inserirlo in un metodo in modo che l'ambito non sia il nodo globale.

+0

Grazie, questo è l'indizio di cui ho bisogno per risolvere questo problema. Mentre avevo già provato il tuo suggerimento, mi ha fatto guardare di nuovo e non avevo considerato che stavo memorizzando parti della mappa in altre variabili - questi stavano causando gli errori. Quindi dovevo disassociarli anch'io. Modificherà la mia domanda per includere le modifiche corrette al codice. Cheers – Sunvic

+0

Si tratta di ~ 8 volte al giorno. Ragazzi, vi dispiacerebbe fornire un esempio più dettagliato di come implementare questa soluzione? –

+0

Non esiste una soluzione semplice in quanto dipende da ciò che hai fatto. Le informazioni fornite qui e la soluzione @Sunvic aggiunta nella parte superiore del suo post dovrebbero essere sufficienti per portare a una soluzione per il proprio codice. –

53

Utilizzare invece JsonSlurperClassic.

Dal Groovy 2.3 (nota: Jenkins 2.7.1 utilizza Groovy 2.4.7) JsonSlurper rendimenti LazyMap invece di HashMap. Ciò rende la nuova implementazione di JsonSlurpernon thread safe e non serializzabile. Ciò lo rende inutilizzabile al di fuori delle funzioni di @NonDSL negli script DSL della pipeline.

Tuttavia è possibile eseguire il fallback su groovy.json.JsonSlurperClassic che supporta il vecchio behavior e potrebbe essere utilizzato in modo sicuro negli script della pipeline.

Esempio

import groovy.json.JsonSlurperClassic 


@NonCPS 
def jsonParse(def json) { 
    new groovy.json.JsonSlurperClassic().parseText(json) 
} 

node('master') { 
    def config = jsonParse(readFile("config.json")) 

    def db = config["database"]["address"] 
    ... 
}  

ps. Dovrai ancora approvare lo JsonSlurperClassic prima che possa essere chiamato.

+2

Potresti dirmi come approvare 'JsonSlurperClassic'? – mybecks

+5

L'amministratore di Jenkins dovrà spostarsi su Manage Jenkins »Approvazione script in-process. – luka5z

+0

Purtroppo ho solo 'hudson.remoting.ProxyException: org.codehaus.groovy.control.MultipleCompilationErrorsException: startup fallita: Script1.groovy: 24: impossibile risolvere la classe groovy.json.JsonSlurperClassic' – dvtoever

7

MODIFICA: Come indicato da @Sunvic nei commenti, la soluzione di seguito non funziona così com'è per gli array JSON.

Mi sono occupato di questo utilizzando JsonSlurper e quindi creando un nuovo HashMap dai risultati lazy. HashMap è Serializable.

Credo che questo richiedesse la lista bianca sia del new HashMap(Map) sia del JsonSlurper.

@NonCPS 
def parseJsonText(String jsonText) { 
    final slurper = new JsonSlurper() 
    return new HashMap<>(slurper.parseText(jsonText)) 
} 
+1

Non mi ha funzionato - ha continuato a ricevere un errore 'Impossibile trovare il costruttore corrispondente per: java.util.HashMap (java.util.ArrayList)'. La documentazione suggerisce che dovrebbe sputare un elenco o una mappa: come si configura per restituire una mappa? – Sunvic

+0

@Sunvic Una buona cattura, i dati che abbiamo analizzato sono sempre oggetti, mai array JSON. Stai cercando di analizzare un array JSON? – mkobit

+0

Ah sì, è un array JSON, sarà così. – Sunvic

1

Il modo plug-gasdotto è stato implementato ha abbastanza gravi implicazioni per codice Groovy non banale. Questo legame spiega come evitare possibili problemi: https://github.com/jenkinsci/pipeline-plugin/blob/master/TUTORIAL.md#serializing-local-variables

Nel vostro caso specifico mi piacerebbe prendere in considerazione l'aggiunta di @NonCPS annotazione slurpJSON e ritorno map-of-maps, invece di oggetto JSON. Non solo il codice sembra più pulito, ma è anche più efficiente, specialmente se questo JSON è complesso.

4

una forma leggermente più generalizzata della risposta da @mkobit che consentirebbe la decodifica degli array così come le mappe sarebbe:

import groovy.json.JsonSlurper 

@NonCPS 
def parseJsonText(String json) { 
    def object = new JsonSlurper().parseText(json) 
    if(object instanceof groovy.json.internal.LazyMap) { 
     return new HashMap<>(object) 
    } 
    return object 
} 

NOTA: Essere consapevoli del fatto che questo sarà solo convertire l'oggetto LazyMap livello superiore a una HashMap. Tutti gli oggetti LazyMap nidificati saranno ancora presenti e continueranno a causare problemi con Jenkins.

0

Ho trovato molto più facile in off docs for Jenkins pipeline

esempio lavoro

import groovy.json.JsonSlurperClassic 


@NonCPS 
def jsonParse(def json) { 
    new groovy.json.JsonSlurperClassic().parseText(json) 
} 

@NonCPS 
def jobs(list) { 
    list 
     .grep { it.value == true } 
     .collect { [ name : it.key.toString(), 
         branch : it.value.toString() ] } 

} 

node { 
    def params = jsonParse(env.choice_app) 
    def forBuild = jobs(params) 
} 

Due to limitations in Workflow - i.e., JENKINS-26481 - it's not really possible to use Groovy closures or syntax that depends on closures, so you can't > do the Groovy standard of using .collectEntries on a list and generating the steps as values for the resulting entries. You also can't use the standard > Java syntax for For loops - i.e., "for (String s: strings)" - and instead have to use old school counter-based for loops.

+0

Consiglierei di usare il passo della pipeline Jenkins readJSON invece - trova maggiori informazioni [qui] (https://jenkins.io/doc/pipeline/steps/pipeline- utilità-passi/# codice-readjson-codice-readjson-da-file-in-the-lavoro). – Sunvic

0

Le altre idee in questo post sono stati utili, ma non proprio tutto quello che cercavo - così ho estratto le parti che si adattano al mio bisogno e ho aggiunto un po 'del mio magix ...

def jsonSlurpLaxWithoutSerializationTroubles(String jsonText) 
{ 
    return new JsonSlurperClassic().parseText(
     new JsonBuilder(
      new JsonSlurper() 
       .setType(JsonParserType.LAX) 
       .parseText(jsonText) 
     ) 
     .toString() 
    ) 
} 

Sì, come ho notato nel mio git commit del codice, "Wildly-ineffecient, ma minuscola coefficiente: soluzione slurp JSON" (che mi va bene per questo scopo). Gli aspetti ho bisogno di risolvere:

  1. Completamente allontanarsi dal problema java.io.NotSerializableException, anche quando il testo JSON definisce contenitori nidificati
  2. lavoro sia per carta e di array contenitori
  3. Supporto LAX parsing (il più importante parte, per la mia situazione)
  4. facile da implementare (anche con i costruttori nidificati scomode che ovviano @NonCPS)
Problemi correlati