Jenkins Pipeline NotSerializableException: groovy.json.internal.LazyMap


80

Đã giải quyết : Nhờ câu trả lời dưới đây của S.Richmond. Tôi cần hủy đặt tất cả các bản đồ được lưu trữ thuộc groovy.json.internal.LazyMaploại có nghĩa là vô hiệu hóa các biến envServersobjectsau khi sử dụng.

Bổ sung : Những người đang tìm kiếm lỗi này có thể muốn sử dụng bước đường dẫn Jenkins readJSONthay thế - tìm thêm thông tin tại đây .


Tôi đang cố gắng sử dụng Jenkins Pipeline để lấy đầu vào từ người dùng được chuyển cho công việc dưới dạng chuỗi json. Sau đó, Pipeline phân tích cú pháp này bằng cách sử dụng slurper và tôi chọn ra thông tin quan trọng. Sau đó, nó sẽ sử dụng thông tin đó để chạy 1 công việc nhiều lần song song với các thông số công việc khác nhau.

Cho đến khi tôi thêm mã bên dưới "## Error when below here is added", tập lệnh sẽ chạy tốt. Ngay cả đoạn mã bên dưới điểm đó cũng sẽ tự chạy. Nhưng khi kết hợp tôi nhận được lỗi dưới đây.

Tôi nên lưu ý rằng công việc được kích hoạt được gọi và chạy thành công nhưng lỗi dưới đây xảy ra và không thực hiện được công việc chính. Bởi vì điều này, công việc chính không chờ đợi sự trở lại của công việc được kích hoạt. Tôi có thể thử / bắt xung quanh build job:tuy nhiên tôi muốn công việc chính đợi cho công việc được kích hoạt kết thúc.

Bất cứ ai có thể hỗ trợ ở đây? Nếu bạn cần thêm thông tin cho tôi biết.

Chúc mừng

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']]

}

Lỗi:

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 org.jenkinsci.plugins.workflow.cps.CpsThreadGroup@5288c

Chỉ cần bản thân mình gặp phải điều này. Bạn đã đạt được tiến bộ nào hơn chưa?
S.Richmond

Câu trả lời:


71

Tôi đã tự mình gặp phải điều này ngày hôm nay và thông qua một số hành động thô bạo, tôi đã tìm ra cả cách giải quyết nó và có khả năng là tại sao.

Có lẽ tốt nhất nên bắt đầu với lý do:

Jenkins có một mô hình mà tất cả các công việc có thể bị gián đoạn, tạm dừng và có thể tiếp tục thông qua khởi động lại máy chủ. Để đạt được điều này, đường ống và dữ liệu của nó phải được tuần tự hóa hoàn toàn - IE nó cần có khả năng lưu trạng thái của mọi thứ. Tương tự, nó cần có khả năng tuần tự hóa trạng thái của các biến toàn cục giữa các nút và công việc con trong bản dựng, đó là điều tôi nghĩ đang xảy ra cho bạn và tôi và tại sao nó chỉ xảy ra nếu bạn thêm bước xây dựng bổ sung đó.

Vì bất kỳ lý do gì JSONObject không thể tuần tự hóa theo mặc định. Tôi không phải là một nhà phát triển Java nên tôi không thể nói nhiều hơn về chủ đề này. Có rất nhiều câu trả lời trên mạng về cách có thể sửa lỗi này đúng cách mặc dù tôi không biết chúng có thể áp dụng như thế nào đối với Groovy và Jenkins. Xem bài đăng này để biết thêm một chút thông tin.

Cách bạn sửa nó:

Nếu bạn biết cách, bạn có thể làm cho JSONObject có thể được tuần tự hóa bằng cách nào đó. Nếu không, bạn có thể giải quyết nó bằng cách đảm bảo không có biến toàn cục nào thuộc loại đó.

Hãy thử bỏ đặt objectvar của bạn hoặc gói nó trong một phương thức để phạm vi của nó không phải là nút toàn cục.


2
Cảm ơn, Đó là manh mối tôi cần để giải quyết vấn đề này. Trong khi tôi đã thử gợi ý của bạn Nó khiến tôi phải xem xét lại và tôi đã không nghĩ rằng tôi đang lưu trữ các phần của bản đồ trong các biến khác - những biến này gây ra lỗi. Vì vậy, tôi cũng cần phải gỡ bỏ chúng. Sẽ sửa đổi câu hỏi của tôi để bao gồm các thay đổi chính xác đối với mã. Chúc mừng
Sunvic

1
Điều này được xem ~ 8 lần một ngày. Các bạn có vui lòng cung cấp một ví dụ chi tiết hơn về cách triển khai giải pháp này không?
Jordan Stefanelli

1
Không có giải pháp đơn giản vì nó phụ thuộc vào những gì bạn đã làm. Thông tin được cung cấp ở đây cũng như giải pháp mà @Sunvic thêm vào ở đầu bài đăng của anh ấy phải đủ để dẫn người ta đến một giải pháp cho mã của riêng họ.
S.Richmond

1
Giải pháp bên dưới, sử dụng JsonSlurperClassic đã khắc phục chính xác vấn đề mà tôi gặp phải, có lẽ nên là lựa chọn được chấp thuận ở đây. Câu trả lời này có giá trị, nhưng nó không phải là giải pháp phù hợp cho vấn đề cụ thể này.
Quartz

@JordanStefanelli Tôi đã đăng mã cho giải pháp của mình. Xem câu trả lời của tôi bên dưới
Nils El-Himoud 13/12/18

127

Sử dụng JsonSlurperClassicthay thế.

Vì Groovy 2.3 ( lưu ý: Jenkins 2.7.1 sử dụng Groovy 2.4.7 ) JsonSlurpertrả về LazyMapthay vì HashMap. Điều này làm cho việc triển khai mới của luồng JsonSlurper không an toàn và không thể tuần tự hóa. Điều này làm cho nó không sử dụng được bên ngoài các chức năng @NonDSL trong các tập lệnh DSL đường ống.

Tuy nhiên, bạn có thể nhớ lại groovy.json.JsonSlurperClassiccái nào hỗ trợ hành vi cũ và có thể được sử dụng an toàn trong các tập lệnh đường ống.

Thí dụ

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. Bạn vẫn cần phải phê duyệt JsonSlurperClassictrước khi nó có thể được gọi.


2
Bạn có thể vui lòng cho tôi biết làm thế nào để chấp thuận JsonSlurperClassic?
mybecks

7
Quản trị viên Jenkins sẽ cần điều hướng đến Quản lý Jenkins »Phê duyệt tập lệnh trong quá trình.
luka5z

Thật không may, tôi chỉ nhận đượchudson.remoting.ProxyException: org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed: Script1.groovy: 24: unable to resolve class groovy.json.JsonSlurperClassic
dvtoever 14/10/16

13
JsonSluperClassic .. Tên này nói rất nhiều về tình trạng hiện tại của phát triển phần mềm
Marcos Brigante

1
Cảm ơn bạn rất nhiều cho lời giải thích chi tiết này. Bạn đã tiết kiệm rất nhiều thời gian của tôi. Giải pháp này hoạt động giống như một sự quyến rũ trong đường ống jenkins của tôi.
Sathish Prakasam

16

CHỈNH SỬA: Như đã được chỉ ra bởi @Sunvic trong các nhận xét, giải pháp bên dưới không hoạt động như đối với Mảng JSON.

Tôi đã xử lý điều này bằng cách sử dụng JsonSlurpervà sau đó tạo mới HashMaptừ kết quả lười biếng. HashMapSerializable.

Tôi tin rằng điều này bắt buộc phải có danh sách trắng của cả cái new HashMap(Map)JsonSlurper.

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

Nhìn chung, tôi khuyên bạn chỉ nên sử dụng plugin Pipeline Utility Steps , vì nó có một readJSONbước có thể hỗ trợ các tệp trong không gian làm việc hoặc văn bản.


1
Không hiệu quả với tôi - tiếp tục gặp lỗi Could not find matching constructor for: java.util.HashMap(java.util.ArrayList). Tài liệu gợi ý rằng nó nên tạo ra một danh sách hoặc một bản đồ - bạn định cấu hình như thế nào để trả lại bản đồ?
Sunvic

@Sunvic Bắt tốt, dữ liệu chúng ta đang phân tích cú pháp luôn là các đối tượng, không bao giờ là mảng JSON. Bạn đang cố gắng phân tích cú pháp một mảng JSON?
mkobit

À vâng, đó là một mảng JSON, sẽ là nó.
Sunvic

Cả câu trả lời này và câu trả lời bên dưới, trên Jenkins, đã đưa ra RejectedEception bởi vì Jenkins chạy rất khó chịu trong sandbox env
yiwen

@yiwen Tôi đã đề cập rằng nó yêu cầu quản trị viên danh sách trắng, nhưng có thể câu trả lời có thể được làm rõ là điều đó có nghĩa là gì?
mkobit

8

Tôi muốn tán thành một trong những câu trả lời: Tôi khuyên bạn chỉ nên sử dụng plugin Pipeline Utility Steps, vì nó có bước readJSON có thể hỗ trợ các tệp trong không gian làm việc hoặc văn bản: https://jenkins.io/doc/pipeline/steps / pipe-tiện ích-bước / # readjson-read-json-from-files-in-the-workspace

script{
  def foo_json = sh(returnStdout:true, script: "aws --output json XXX").trim()
  def foo = readJSON text: foo_json
}

Điều này KHÔNG yêu cầu bất kỳ danh sách trắng hoặc nội dung bổ sung nào.


6

Đây là câu trả lời chi tiết đã được yêu cầu.

Việc chưa đặt đã làm việc cho tôi:

String res = sh(script: "curl --header 'X-Vault-Token: ${token}' --request POST --data '${payload}' ${url}", returnStdout: true)
def response = new JsonSlurper().parseText(res)
String value1 = response.data.value1
String value2 = response.data.value2

// unset response because it's not serializable and Jenkins throws NotSerializableException.
response = null

Tôi đọc các giá trị từ phản hồi được phân tích cú pháp và khi tôi không cần đối tượng nữa, tôi bỏ đặt nó.


5

Một dạng câu trả lời tổng quát hơn một chút từ @mkobit sẽ cho phép giải mã các mảng cũng như bản đồ sẽ là:

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
}

LƯU Ý: Hãy lưu ý rằng điều này sẽ chỉ chuyển đổi đối tượng LazyMap cấp cao nhất thành HashMap. Mọi đối tượng LazyMap lồng nhau sẽ vẫn ở đó và tiếp tục gây ra sự cố với Jenkins.


2

Cách plugin đường ống đã được triển khai có ý nghĩa khá nghiêm trọng đối với mã Groovy không tầm thường. Liên kết này giải thích cách tránh các sự cố có thể xảy ra: https://github.com/jenkinsci/pipeline-plugin/blob/master/TUTORIAL.md#serializing-local-variables

Trong trường hợp cụ thể của bạn, tôi sẽ xem xét thêm @NonCPSchú thích vào slurpJSONvà trả về bản đồ của bản đồ thay vì đối tượng JSON. Mã không chỉ trông gọn gàng hơn mà còn hiệu quả hơn, đặc biệt nếu JSON đó phức tạp.


2

Theo các phương pháp hay nhất được đăng trên blog Jenkins ( phương pháp hay nhất về khả năng mở rộng đường ống ), bạn nên sử dụng các công cụ hoặc tập lệnh dòng lệnh cho loại công việc này:

Gotcha: đặc biệt tránh phân tích cú pháp Pipeline XML hoặc JSON bằng cách sử dụng XmlSlurper và JsonSlurper của Groovy! Rất thích các công cụ hoặc tập lệnh dòng lệnh.

Tôi. Việc triển khai Groovy rất phức tạp và do đó dễ gãy hơn trong việc sử dụng Đường ống.

ii. XmlSlurper và JsonSlurper có thể mang bộ nhớ và chi phí CPU cao trong đường ống

iii. xmllint và xmlstartlet là các công cụ dòng lệnh cung cấp khả năng trích xuất XML qua xpath

iv. jq cung cấp chức năng tương tự cho JSON

v. Các công cụ trích xuất này có thể được kết hợp với nhau để cuộn tròn hoặc tiện ích để tìm nạp thông tin từ API HTTP

Do đó, nó giải thích tại sao hầu hết các giải pháp được đề xuất trên trang này đều bị chặn theo mặc định bởi hộp cát của plugin tập lệnh bảo mật Jenkins.

Triết lý ngôn ngữ của Groovy gần với Bash hơn là Python hoặc Java. Ngoài ra, điều đó có nghĩa là không phải tự nhiên mà làm những công việc phức tạp và nặng nhọc ở Groovy bản địa.

Do đó, cá nhân tôi quyết định sử dụng những điều sau:

sh('jq <filters_and_options> file.json')

Xem bài hướng dẫn jqChọn đối tượng với bài đăng jq stackoverflow để được trợ giúp thêm.

Điều này hơi phản trực quan vì Groovy cung cấp nhiều phương thức chung không có trong danh sách trắng mặc định.

Nếu bạn vẫn quyết định sử dụng ngôn ngữ Groovy cho hầu hết công việc của mình, với hộp cát được bật và sạch (điều này không dễ dàng vì không tự nhiên), tôi khuyên bạn nên kiểm tra danh sách trắng cho phiên bản plugin tập lệnh bảo mật của mình để biết khả năng của bạn là gì: Script danh sách trắng plugin bảo mật


2

Bạn có thể sử dụng chức năng sau để chuyển đổi LazyMap thành LinkedHashMap thông thường (nó sẽ giữ nguyên thứ tự của dữ liệu gốc):

LinkedHashMap nonLazyMap (Map lazyMap) {
    LinkedHashMap res = new LinkedHashMap()
    lazyMap.each { key, value ->
        if (value instanceof Map) {
            res.put (key, nonLazyMap(value))
        } else if (value instanceof List) {
            res.put (key, value.stream().map { it instanceof Map ? nonLazyMap(it) : it }.collect(Collectors.toList()))
        } else {
            res.put (key, value)
        }
    }
    return res
}

... 

LazyMap lazyMap = new JsonSlurper().parseText (jsonText)
Map serializableMap = nonLazyMap(lazyMap);

hoặc tốt hơn là sử dụng bước readJSON như đã lưu ý trong các nhận xét trước đó:

Map serializableMap = readJSON text: jsonText

1

Các ý tưởng khác trong bài đăng này rất hữu ích, nhưng không hoàn toàn là tất cả những gì tôi đang tìm kiếm - vì vậy tôi đã trích xuất các phần phù hợp với nhu cầu của mình và thêm một số magix của riêng tôi ...

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

Có, như tôi đã lưu ý trong git cam kết mã của riêng tôi, "Cực kỳ không hiệu quả, nhưng hệ số nhỏ: giải pháp JSON slurp" (tôi không sao với mục đích này). Các khía cạnh tôi cần giải quyết:

  1. Hoàn toàn thoát khỏi java.io.NotSerializableExceptionvấn đề, ngay cả khi văn bản JSON xác định các vùng chứa lồng nhau
  2. Làm việc cho cả vùng chứa bản đồ và mảng
  3. Hỗ trợ phân tích cú pháp LAX (phần quan trọng nhất, đối với tình huống của tôi)
  4. Dễ thực hiện (ngay cả với các hàm tạo lồng nhau khó xử lý @NonCPS)

1

Noob sai lầm về phía tôi. Đã chuyển mã một số từ một plugin đường ống cũ, jenkins 1.6? tới máy chủ chạy jenkins 2.x mới nhất.

Không thành công vì lý do này: "java.io.NotSerializableException: groovy.lang.IntRange" Tôi đã đọc đi đọc lại bài đăng này nhiều lần vì lỗi trên. Realized: for (num in 1..numSlaves) {IntRange - kiểu đối tượng không thể tuần tự hóa.

Viết lại ở dạng đơn giản: for (num = 1; num <= numSlaves; num ++)

Tất cả đều tốt với thế giới.

Tôi không sử dụng java hoặc groovy rất thường xuyên.

Cảm ơn các bạn.


0

Tôi đã tìm thấy cách dễ dàng hơn trong các tài liệu tắt cho đường ống Jenkins

Ví dụ công việc

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

Do các hạn chế trong Dòng công việc - tức là JENKINS-26481 - không thực sự có thể sử dụng các bao đóng hoặc cú pháp Groovy phụ thuộc vào các bao đóng, vì vậy bạn không thể> thực hiện tiêu chuẩn Groovy khi sử dụng .collectEntries trên danh sách và tạo các bước dưới dạng giá trị cho các mục kết quả. Bạn cũng không thể sử dụng cú pháp Java chuẩn> cho vòng lặp For - tức là "for (String s: string)" - và thay vào đó bạn phải sử dụng các vòng lặp for dựa trên bộ đếm cũ.


1
Thay vào đó, bạn nên sử dụng bước đường ống Jenkins readJSON - tìm thêm thông tin tại đây .
Sunvic
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.