В моей общей библиотеке Jenkins я создал класс ArtifactManager, который выполняет очистку докера из Artifactory при удалении ветки.
Когда нужно удалить очень большой каталог образов докеров (~ 50 ГБ), я получаю неожиданное прерывание:
java.nio.channels.ClosedByInterruptException
at java.base/java.nio.channels.spi.AbstractInterruptibleChannel.end(AbstractInterruptibleChannel.java:199)
at java.base/sun.nio.ch.FileChannelImpl.endBlocking(FileChannelImpl.java:162)
at java.base/sun.nio.ch.FileChannelImpl.write(FileChannelImpl.java:285)
at org.jenkinsci.plugins.workflow.support.pickles.serialization.RiverWriter.<init>(RiverWriter.java:109)
at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup.saveProgram(CpsThreadGroup.java:560)
at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup.saveProgram(CpsThreadGroup.java:537)
at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup.saveProgramIfPossible(CpsThreadGroup.java:520)
at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup.run(CpsThreadGroup.java:444)
at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup.access$400(CpsThreadGroup.java:97)
at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup$2.call(CpsThreadGroup.java:315)
at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup$2.call(CpsThreadGroup.java:279)
at org.jenkinsci.plugins.workflow.cps.CpsVmExecutorService$2.call(CpsVmExecutorService.java:67)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
at hudson.remoting.SingleLaneExecutorService$1.run(SingleLaneExecutorService.java:139)
at jenkins.util.ContextResettingExecutorService$1.run(ContextResettingExecutorService.java:28)
at jenkins.security.ImpersonatingExecutorService$1.run(ImpersonatingExecutorService.java:68)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at java.base/java.lang.Thread.run(Thread.java:829)
Finished: FAILURE
Мой код не включен в трассировку стека… Код, который выполняет HTTP-запросы:
def deleteArtifact(String pathOnServer){
def responseCode, data
Boolean deleted = true
Logger.printInfo(steps, "Deleting $server/$pathOnServer artifact from server")
steps.withCredentials([steps.usernamePassword(credentialsId: 'artifactory_user', passwordVariable: 'PASSWORD', usernameVariable: 'USERNAME')]){
(responseCode, data) = sendApiRequest("$server/$pathOnServer", "DELETE")
}
//if !2xxSuccessful() -> Http response codes family - 1xx: Informational, 2xx: Success, 3xx: Redirection, 4xx: Client Error, 5xx: Server Error
if ((responseCode / 100 as int) != 2){
deleted = false
Logger.printError(steps, "Failed to delete: `$pathOnServer` from `$server`. Status Code: $responseCode")
Logger.printError(steps, "Data: $data")
}
return deleted
}
def sendApiRequest(String query, String httpMethod, String contentType = "", String data = ""){
def responseCode
def responseData
def conn = new URL("${this.protocol}://$query").openConnection()
//Trying to increase timeout
conn.setConnectTimeout(15 * 60 *60 * 1000);
conn.setReadTimeout(15 * 60 *60 * 1000);
def auth = "${steps.env.USERNAME}:${steps.env.PASSWORD}".getBytes().encodeBase64().toString()
conn.setRequestProperty("Authorization", "Basic ${auth}")
conn.setRequestMethod(httpMethod)
if (contentType) conn.setRequestProperty( "Content-Type", contentType);
if (data){
conn.setDoOutput(true)
conn.getOutputStream().write(data.getBytes("UTF-8"));
//Note: the POST will start when you try to read a value from the HttpURLConnection, such as responseCode, inputStream.text, or getHeaderField('...'). (https://stackoverflow.com/a/47489805/10025322)
}
responseCode = conn.getResponseCode()
try{
responseData = conn.getInputStream().getText()
}catch(IOException e){
responseData = e.getMessage()
}
conn = null
return [responseCode, responseData]
}
Я также пытался использовать другую библиотеку для выполнения запроса, но все еще получаю исключение, когда Artifactory отвечает медленно (удалить огромный каталог):
def sendApiRequest(String query, String httpMethod, String contentType = "", String data = ""){
def responseCode
def responseData
def http = new HTTPBuilder("${this.protocol}://$query")
http.request(Method.valueOf(httpMethod)) {
headers.'Authorization' = "Basic ${steps.env.USERNAME}:${steps.env.PASSWORD}".getBytes().encodeBase64().toString()
if (contentType) {
headers.'Content-Type' = contentType
requestContentType = contentType
}
if (data) {
body = data
}
response.success = { resp, reader ->
responseCode = resp.statusLine.statusCode
responseData = reader.text
}
response.failure = { resp, reader ->
responseCode = resp.statusLine.statusCode
responseData = reader.text
}
}
echo("RESPONSE_CODE: " + responseCode.toString() + " RESPONSE_DATA: " + responseData.toString())
return [responseCode, responseData]
}
Я обнаружил, что ClosedByInterruptException может возникнуть по следующим причинам:
Любая идея, как справиться/обходной путь?
Предоставленный код не работает из-за ошибки в ядре Jenkins (эта проблема была отмечена как исправленная в jenkins >= 2.332.1LTS или 2.335)
В качестве обходного пути вы можете использовать:
def sendApiRequest(String query, String httpMethod, String contentType = "", String data = ""){
/*
Caller must wrap function call with 'withCredentials' statement
with 'USERNAME' and 'PASSWORD' environment varaiables
*/
String apiCmd = """curl -s -w '###%{http_code}' -X '${httpMethod}' \
'${this.protocol}://$query' \
${contentType ? "-H 'Content-Type: $contentType'" : ''} \
-u "\$USERNAME:\$PASSWORD" \
${data ? "-d '${data}'" : ''}"""
def response = steps.sh(returnStdout: true, script: apiCmd, label: "Send API request to Artifactory").trim()
def responseCode = response.split("###")[1]
def responseData = response.split("###")[0]
try{
responseCode = responseCode.toInteger()
} catch (NumberFormatException e) {
throw new RuntimeException("Invalid status code: `$responseCode` cannot cast to integer")
}
return [responseCode, responseData]
}