fix repeatTaskUntilSuccess and k3d test
This commit is contained in:
parent
211f66d1ce
commit
3bd9cdb6d9
8 changed files with 103 additions and 42 deletions
|
@ -285,8 +285,15 @@ open class Prov protected constructor(
|
||||||
// post-handling
|
// post-handling
|
||||||
val returnValue =
|
val returnValue =
|
||||||
if (mode == ResultMode.LAST) {
|
if (mode == ResultMode.LAST) {
|
||||||
if (internalResultIsLeaf(resultIndex) || taskName == "cmd")
|
if (internalResultIsLeaf(resultIndex) || taskName == "cmd" || taskName?.replace(" (requireLast)", "") == "repeatTaskUntilSuccess") { // todo: improve and remove replace function
|
||||||
res.copy() else ProvResult(res.success)
|
// for a leaf (task with mo subtask) or tasks "cmd" resp. "repeatUntilTrue" provide also out and err of original results
|
||||||
|
// because results of cmd and leafs are not included in the reporting
|
||||||
|
// and the caller of repeatUntilTrue might need the complete result (incl. out and err) and not only success value
|
||||||
|
res.copy()
|
||||||
|
} else {
|
||||||
|
// just pass success value, no other data of the original result
|
||||||
|
ProvResult(res.success)
|
||||||
|
}
|
||||||
} else if (mode == ResultMode.ALL) {
|
} else if (mode == ResultMode.ALL) {
|
||||||
// leaf
|
// leaf
|
||||||
if (internalResultIsLeaf(resultIndex)) res.copy()
|
if (internalResultIsLeaf(resultIndex)) res.copy()
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.core
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Repeats task until it returns success
|
||||||
|
*/
|
||||||
|
fun Prov.repeatTaskUntilSuccess(times: Int, sleepInSec: Int, func: Prov.() -> ProvResult) = requireLast {
|
||||||
|
require(times > 0)
|
||||||
|
var result = ProvResult(false, err = "Internal error") // Will only be returned if function is not executed at all, otherwise func's last result is returned
|
||||||
|
for (i in 1..times) {
|
||||||
|
result = func()
|
||||||
|
if (result.success)
|
||||||
|
return@requireLast result
|
||||||
|
Thread.sleep(sleepInSec * 1000L)
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
|
@ -8,20 +8,6 @@ import org.domaindrivenarchitecture.provs.core.tags.Api
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.net.InetAddress
|
import java.net.InetAddress
|
||||||
|
|
||||||
/**
|
|
||||||
* Repeats task until it returns success
|
|
||||||
*/
|
|
||||||
fun Prov.repeatTask(times: Int, sleepInSec: Int, func: Prov.() -> ProvResult) = requireLast {
|
|
||||||
require(times > 0)
|
|
||||||
var result = ProvResult(false, err = "Internal error") // Will only be returned if function is not executed at all, otherwise func's result is returned
|
|
||||||
for (i in 1..times) {
|
|
||||||
result = func()
|
|
||||||
if (result.success)
|
|
||||||
return@requireLast result
|
|
||||||
Thread.sleep(sleepInSec * 1000L)
|
|
||||||
}
|
|
||||||
return@requireLast result
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the name of the calling function but excluding some functions of the prov framework
|
* Returns the name of the calling function but excluding some functions of the prov framework
|
||||||
|
@ -29,7 +15,7 @@ fun Prov.repeatTask(times: Int, sleepInSec: Int, func: Prov.() -> ProvResult) =
|
||||||
* Note: names of inner functions (i.e. which are defined inside other functions) are not
|
* Note: names of inner functions (i.e. which are defined inside other functions) are not
|
||||||
* supported in the sense that always the name of the outer function is returned instead.
|
* supported in the sense that always the name of the outer function is returned instead.
|
||||||
*/
|
*/
|
||||||
fun getCallingMethodName(): String? {
|
internal fun getCallingMethodName(): String? {
|
||||||
val offsetVal = 1
|
val offsetVal = 1
|
||||||
val exclude = arrayOf("task", "def", "record", "invoke", "invoke0", "handle", "task\$default", "def\$default", "addResultToEval", "handle\$default")
|
val exclude = arrayOf("task", "def", "record", "invoke", "invoke0", "handle", "task\$default", "def\$default", "addResultToEval", "handle\$default")
|
||||||
// suffixes are also ignored as method names but will be added as suffix in the evaluation results
|
// suffixes are also ignored as method names but will be added as suffix in the evaluation results
|
||||||
|
@ -55,6 +41,7 @@ fun getCallingMethodName(): String? {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// --------------------------- String extensions ----------------------------
|
||||||
fun String.escapeNewline(): String = replace("\r", "\\r").replace("\n", "\\n")
|
fun String.escapeNewline(): String = replace("\r", "\\r").replace("\n", "\\n")
|
||||||
fun String.escapeControlChars(): String = replace("\r", "\\r").replace("\n", "\\n").replace("\t", "\\t").replace("[\\p{Cntrl}]".toRegex(), "\\?")
|
fun String.escapeControlChars(): String = replace("\r", "\\r").replace("\n", "\\n").replace("\t", "\\t").replace("[\\p{Cntrl}]".toRegex(), "\\?")
|
||||||
fun String.escapeBackslash(): String = replace("\\", "\\\\")
|
fun String.escapeBackslash(): String = replace("\\", "\\\\")
|
||||||
|
@ -66,7 +53,6 @@ fun String.escapeSingleQuoteForShell(): String = replace("'", "'\"'\"'")
|
||||||
fun String.escapeProcentForPrintf(): String = replace("%", "%%")
|
fun String.escapeProcentForPrintf(): String = replace("%", "%%")
|
||||||
fun String.endingWithFileSeparator(): String = if (length > 0 && (last() != fileSeparatorChar())) this + fileSeparator() else this
|
fun String.endingWithFileSeparator(): String = if (length > 0 && (last() != fileSeparatorChar())) this + fileSeparator() else this
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Put String between double quotes and escapes chars that need to be escaped (by backslash) for use in Unix Shell String
|
* Put String between double quotes and escapes chars that need to be escaped (by backslash) for use in Unix Shell String
|
||||||
* I.e. the following chars are escaped: backslash, backtick, double quote, dollar
|
* I.e. the following chars are escaped: backslash, backtick, double quote, dollar
|
||||||
|
@ -79,6 +65,7 @@ fun String.escapeForShell(): String {
|
||||||
return this.escapeBackslash().escapeBacktick().escapeDoubleQuote().escapeDollar()
|
return this.escapeBackslash().escapeBacktick().escapeDoubleQuote().escapeDollar()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an echo command for the given String, which will be escaped for the bash
|
* Returns an echo command for the given String, which will be escaped for the bash
|
||||||
*/
|
*/
|
||||||
|
@ -162,6 +149,10 @@ fun docker(
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if sudo is required to run docker locally, otherwise returns false.
|
||||||
|
* Throws an IllegalStateException if docker cannot be run locally at all.
|
||||||
|
*/
|
||||||
fun checkSudoRequiredForDocker(): Boolean {
|
fun checkSudoRequiredForDocker(): Boolean {
|
||||||
return if (local().chk("docker -v")) {
|
return if (local().chk("docker -v")) {
|
||||||
false
|
false
|
||||||
|
|
|
@ -3,16 +3,20 @@ package org.domaindrivenarchitecture.provs.extensions.server_software.k3s.domain
|
||||||
import org.domaindrivenarchitecture.provs.core.Prov
|
import org.domaindrivenarchitecture.provs.core.Prov
|
||||||
import org.domaindrivenarchitecture.provs.core.docker.provideContainer
|
import org.domaindrivenarchitecture.provs.core.docker.provideContainer
|
||||||
import org.domaindrivenarchitecture.provs.core.echoCommandForTextWithNewlinesReplaced
|
import org.domaindrivenarchitecture.provs.core.echoCommandForTextWithNewlinesReplaced
|
||||||
import org.domaindrivenarchitecture.provs.core.repeatTask
|
import org.domaindrivenarchitecture.provs.core.repeatTaskUntilSuccess
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Runs a k3s server and a k3s agent as containers.
|
* Runs a k3s server and a k3s agent as containers.
|
||||||
|
* Copies the kubeconfig from container to the default location: $HOME/.kube/config
|
||||||
*/
|
*/
|
||||||
fun Prov.installK3sAsContainers(token: String = "12345678901234") = task {
|
fun Prov.installK3sAsContainers(token: String = "12345678901234") = task {
|
||||||
cmd("docker volume create k3s-server")
|
cmd("docker volume create k3s-server")
|
||||||
provideContainer("k3s-server", "rancher/k3s", command = "server --cluster-init", options = "-d --privileged --tmpfs /run --tmpfs /var/run -e K3S_TOKEN=$token -e K3S_KUBECONFIG_OUTPUT=./kubeconfig.yaml -e K3S_KUBECONFIG_MODE=666 -v k3s-server:/var/lib/rancher/k3s:z -p 6443:6443 -p 80:80 -p 443:443")
|
provideContainer("k3s-server", "rancher/k3s", command = "server --cluster-init", options =
|
||||||
provideContainer("k3s-agent", "rancher/k3s", options = "-d --privileged --tmpfs /run --tmpfs /var/run -e K3S_TOKEN=$token -e K3S_URL=https://server:6443")
|
"-d --privileged --tmpfs /run --tmpfs /var/run " +
|
||||||
|
"-e K3S_TOKEN=$token -e K3S_KUBECONFIG_OUTPUT=./kubeconfig.yaml -e K3S_KUBECONFIG_MODE=666 " +
|
||||||
|
"-v k3s-server:/var/lib/rancher/k3s:z -p 6443:6443 -p 80:80 -p 443:443 " +
|
||||||
|
"--ulimit nproc=65535 --ulimit nofile=65535:65535")
|
||||||
|
|
||||||
// wait for config file
|
// wait for config file
|
||||||
cmd("export timeout=60; while [ ! -f /var/lib/docker/volumes/k3s-server/_data/server/kubeconfig.yaml ]; do if [ \"${'$'}timeout\" == 0 ]; then echo \"ERROR: Timeout while waiting for file.\"; break; fi; sleep 1; ((timeout--)); done")
|
cmd("export timeout=60; while [ ! -f /var/lib/docker/volumes/k3s-server/_data/server/kubeconfig.yaml ]; do if [ \"${'$'}timeout\" == 0 ]; then echo \"ERROR: Timeout while waiting for file.\"; break; fi; sleep 1; ((timeout--)); done")
|
||||||
|
@ -29,7 +33,7 @@ fun Prov.installK3sAsContainers(token: String = "12345678901234") = task {
|
||||||
* Prerequisite: Kubectl has to be installed
|
* Prerequisite: Kubectl has to be installed
|
||||||
*/
|
*/
|
||||||
fun Prov.applyK8sConfig(configAsYaml: String, kubectlCommand: String = "kubectl") = task {
|
fun Prov.applyK8sConfig(configAsYaml: String, kubectlCommand: String = "kubectl") = task {
|
||||||
repeatTask(6, 10) {
|
repeatTaskUntilSuccess(6, 10) {
|
||||||
cmd(echoCommandForTextWithNewlinesReplaced(configAsYaml) + " | $kubectlCommand apply -f -")
|
cmd(echoCommandForTextWithNewlinesReplaced(configAsYaml) + " | $kubectlCommand apply -f -")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ package org.domaindrivenarchitecture.provs.extensions.server_software.k3s.infras
|
||||||
|
|
||||||
import org.domaindrivenarchitecture.provs.core.Prov
|
import org.domaindrivenarchitecture.provs.core.Prov
|
||||||
import org.domaindrivenarchitecture.provs.core.ProvResult
|
import org.domaindrivenarchitecture.provs.core.ProvResult
|
||||||
import org.domaindrivenarchitecture.provs.core.repeatTask
|
import org.domaindrivenarchitecture.provs.core.repeatTaskUntilSuccess
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -10,14 +10,14 @@ import org.domaindrivenarchitecture.provs.core.repeatTask
|
||||||
*/
|
*/
|
||||||
fun Prov.checkAppleService(host: String = "127.0.0.1") = requireLast {
|
fun Prov.checkAppleService(host: String = "127.0.0.1") = requireLast {
|
||||||
// repeat required as curl may return with "empty reply from server" or with "Recv failure: Connection reset by peer"
|
// repeat required as curl may return with "empty reply from server" or with "Recv failure: Connection reset by peer"
|
||||||
val res = repeatTask(12, 10) {
|
val res = repeatTaskUntilSuccess(12, 10) {
|
||||||
cmd("curl -m 30 $host/apple")
|
cmd("curl -m 30 $host/apple")
|
||||||
}.out?.trim()
|
}.out?.trim()
|
||||||
|
|
||||||
return@requireLast if ("apple" == res) {
|
if ("apple" == res) {
|
||||||
ProvResult(true, out = res)
|
ProvResult(true, out = res)
|
||||||
} else {
|
} else {
|
||||||
ProvResult(false,err = "Url $host/apple did not return text \"apple\" but returned: $res")
|
ProvResult(false, err = "Url $host/apple did not return text \"apple\" but returned: $res")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -289,7 +289,8 @@ internal class ProvTest {
|
||||||
System.setErr(PrintStream(errContent))
|
System.setErr(PrintStream(errContent))
|
||||||
|
|
||||||
// when
|
// when
|
||||||
Prov.newInstance(name = "test instance with no progress info", progressType = ProgressType.NONE).methodThatProvidesSomeOutput()
|
Prov.newInstance(name = "test instance with no progress info", progressType = ProgressType.NONE)
|
||||||
|
.methodThatProvidesSomeOutput()
|
||||||
|
|
||||||
// then
|
// then
|
||||||
System.setOut(originalOut)
|
System.setOut(originalOut)
|
||||||
|
@ -298,18 +299,18 @@ internal class ProvTest {
|
||||||
println(outContent.toString())
|
println(outContent.toString())
|
||||||
|
|
||||||
val expectedOutput =
|
val expectedOutput =
|
||||||
"============================================== SUMMARY (test instance) ============================================== \n" +
|
"============================================== SUMMARY (test instance with no progress info) ============================================== \n" +
|
||||||
"> \u001B[92mSuccess\u001B[0m -- methodThatProvidesSomeOutput (requireLast) \n" +
|
"> \u001B[92mSuccess\u001B[0m -- methodThatProvidesSomeOutput (requireLast) \n" +
|
||||||
"---> \u001B[91mFAILED\u001B[0m -- checkPrereq_evaluateToFailure (requireLast) -- Error: This is a test error.\n" +
|
"---> \u001B[91mFAILED\u001B[0m -- checkPrereq_evaluateToFailure (requireLast) -- Error: This is a test error.\n" +
|
||||||
"---> \u001B[92mSuccess\u001B[0m -- sh \n" +
|
"---> \u001B[92mSuccess\u001B[0m -- sh \n" +
|
||||||
"------> \u001B[92mSuccess\u001B[0m -- cmd [/bin/bash, -c, echo -Start test-]\n" +
|
"------> \u001B[92mSuccess\u001B[0m -- cmd [/bin/bash, -c, echo -Start test-]\n" +
|
||||||
"------> \u001B[92mSuccess\u001B[0m -- cmd [/bin/bash, -c, echo Some output]\n" +
|
"------> \u001B[92mSuccess\u001B[0m -- cmd [/bin/bash, -c, echo Some output]\n" +
|
||||||
"---> \u001B[92mSuccess\u001B[0m -- sh \n" +
|
"---> \u001B[92mSuccess\u001B[0m -- sh \n" +
|
||||||
"------> \u001B[92mSuccess\u001B[0m -- cmd [/bin/bash, -c, echo -End test-]\n" +
|
"------> \u001B[92mSuccess\u001B[0m -- cmd [/bin/bash, -c, echo -End test-]\n" +
|
||||||
"----------------------------------------------------------------------------------------------------- \n" +
|
"----------------------------------------------------------------------------------------------------- \n" +
|
||||||
"Overall > \u001B[92mSuccess\u001B[0m\n" +
|
"Overall > \u001B[92mSuccess\u001B[0m\n" +
|
||||||
"============================================ SUMMARY END ============================================ \n" +
|
"============================================ SUMMARY END ============================================ \n" +
|
||||||
"\n"
|
"\n"
|
||||||
|
|
||||||
assertEquals(expectedOutput, outContent.toString().replace("\r", ""))
|
assertEquals(expectedOutput, outContent.toString().replace("\r", ""))
|
||||||
}
|
}
|
||||||
|
@ -465,4 +466,6 @@ internal class ProvTest {
|
||||||
// then
|
// then
|
||||||
assertEquals(true, res.success)
|
assertEquals(true, res.success)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.core
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.test.testLocal
|
||||||
|
import org.junit.jupiter.api.Assertions.*
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
|
internal class TaskFunctionsKtTest {
|
||||||
|
|
||||||
|
var count = 1
|
||||||
|
fun Prov.altenateSuccessAndFailure() = task {
|
||||||
|
if (count == 0) {
|
||||||
|
count = 1
|
||||||
|
ProvResult(true, out = "0")
|
||||||
|
} else {
|
||||||
|
count--
|
||||||
|
ProvResult(false, err = "1")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Prov.repeating() = requireLast {
|
||||||
|
val res = repeatTaskUntilSuccess(4, 1) {
|
||||||
|
altenateSuccessAndFailure()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res.success && ("0" == res.out?.trim())) {
|
||||||
|
ProvResult(true)
|
||||||
|
} else {
|
||||||
|
ProvResult(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun repeat_and_requireLast() {
|
||||||
|
assertTrue(testLocal().repeating().success)
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,21 +10,24 @@ import org.domaindrivenarchitecture.provs.extensions.server_software.k3s.infrast
|
||||||
import org.domaindrivenarchitecture.provs.test.tags.ContainerTest
|
import org.domaindrivenarchitecture.provs.test.tags.ContainerTest
|
||||||
import org.domaindrivenarchitecture.provs.test.tags.NonCi
|
import org.domaindrivenarchitecture.provs.test.tags.NonCi
|
||||||
import org.junit.jupiter.api.Assertions.assertTrue
|
import org.junit.jupiter.api.Assertions.assertTrue
|
||||||
|
import org.junit.jupiter.api.Disabled
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
internal class K3dKtTest {
|
internal class K3dKtTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@Disabled // remove line and execute manually as this test may take several minutes
|
||||||
@ContainerTest
|
@ContainerTest
|
||||||
@NonCi
|
@NonCi
|
||||||
fun installK3sAsContainers() {
|
fun installK3sAsContainers() {
|
||||||
|
|
||||||
|
// given
|
||||||
val containerName = "alpine-docker-dind"
|
val containerName = "alpine-docker-dind"
|
||||||
local().task {
|
local().task {
|
||||||
provideContainer(
|
provideContainer(
|
||||||
containerName,
|
containerName,
|
||||||
"yobasystems/alpine-docker:dind-amd64",
|
"yobasystems/alpine-docker:dind-amd64",
|
||||||
// ContainerStartMode.CREATE_NEW_KILL_EXISTING, // for re-create a potentially existing container
|
ContainerStartMode.CREATE_NEW_KILL_EXISTING, // for re-create a potentially existing container
|
||||||
sudo = false,
|
sudo = false,
|
||||||
options = "--privileged"
|
options = "--privileged"
|
||||||
)
|
)
|
||||||
|
@ -35,7 +38,6 @@ internal class K3dKtTest {
|
||||||
|
|
||||||
val result = docker(containerName, sudo = false).task {
|
val result = docker(containerName, sudo = false).task {
|
||||||
|
|
||||||
// given
|
|
||||||
cmd("apk update")
|
cmd("apk update")
|
||||||
cmd("apk add sudo curl")
|
cmd("apk add sudo curl")
|
||||||
task(
|
task(
|
||||||
|
@ -49,9 +51,9 @@ internal class K3dKtTest {
|
||||||
""".trimIndent())
|
""".trimIndent())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// when
|
// when
|
||||||
installK3sAsContainers()
|
installK3sAsContainers()
|
||||||
|
|
||||||
applyK8sConfig(appleConfig())
|
applyK8sConfig(appleConfig())
|
||||||
|
|
||||||
cmd("kubectl wait --for=condition=ready --timeout=600s pod apple-app")
|
cmd("kubectl wait --for=condition=ready --timeout=600s pod apple-app")
|
||||||
|
|
Loading…
Reference in a new issue