Skip to content

Commit

Permalink
Merge pull request #4120 from irwinsun/issue_3138
Browse files Browse the repository at this point in the history
feat: finally stage #3138
  • Loading branch information
irwinsun authored May 7, 2021
2 parents 6c8d410 + f2d4724 commit 1199c75
Show file tree
Hide file tree
Showing 39 changed files with 1,385 additions and 453 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ object EnvUtils {
): String {
val newValue = StringBuilder()
var index = 0
while (index < value!!.length) {
while (index < value.length) {
val c = value[index]
if (checkPrefix(c, index, value)) {
val inside = StringBuilder()
Expand All @@ -77,7 +77,7 @@ object EnvUtils {
): String {
val newValue = StringBuilder()
var index = 0
while (index < command!!.length) {
while (index < command.length) {
val c = command[index]
if (c == '$' && (index + 1) < command.length && command[index + 1] == '{') {
val inside = StringBuilder()
Expand Down Expand Up @@ -177,4 +177,4 @@ object EnvUtils {

private fun checkPrefix(c: Char, index: Int, value: String) =
c == '$' && (index + 2) < value.length && value[index + 1] == '{' && value[index + 2] == '{'
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ data class Stage(
val customBuildEnv: Map<String, String>? = null,
@ApiModelProperty("是否启用容器失败快速终止阶段", required = false)
val fastKill: Boolean? = false,
@ApiModelProperty("标识是否为FinallyStage,每个Model只能包含一个FinallyStage,并且处于最后位置", required = false)
val finally: Boolean = false,
@ApiModelProperty("当前Stage是否能重试", required = false)
var canRetry: Boolean? = false,
@ApiModelProperty("流程控制选项", required = true)
var stageControlOption: StageControlOption? = null // 为了兼容旧数据,所以定义为可空以及var
)
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,11 @@ package com.tencent.devops.common.pipeline.enums
* @version 1.0
*/
enum class JobRunCondition {
STAGE_RUNNING, // Stage开始运行时
STAGE_RUNNING, // 当前Stage开始运行时
CUSTOM_VARIABLE_MATCH, // 自定义变量全部满足时运行
CUSTOM_VARIABLE_MATCH_NOT_RUN, // 自定义变量全部满足时不运行
CUSTOM_CONDITION_MATCH // 满足以下自定义条件时运行
CUSTOM_CONDITION_MATCH, // 满足以下自定义条件时运行
PREVIOUS_STAGE_SUCCESS, // 上游 Stage 成功时
PREVIOUS_STAGE_FAILED, // 上游 Stage 失败时
PREVIOUS_STAGE_CANCEL // 上游 Stage 取消时
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ interface ModelCheckPlugin {
*/
fun checkModelIntegrity(model: Model, projectId: String?)

fun checkJob(jobContainer: Container, projectId: String, pipelineId: String, userId: String)
fun checkJob(jobContainer: Container, projectId: String, pipelineId: String, userId: String, finallyStage: Boolean)

/**
* 清理Model--不删除里面的Element内的逻辑
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,13 +124,16 @@ abstract class Element(
open fun findFirstTaskIdByStartType(startType: StartType): String = ""

/**
* 根据参数变量[params]初始化出插件是否跳过
* 根据参数变量[params]以及是否在[finallyStage],来初始化出插件是否跳过
* 当插件处于finally Stage下时插件,除非是显式的通过[params]指明要跳过或者本身的[isElementEnable]设置为未启用插件会返回SKIP
*/
fun initStatus(params: Map<String, Any>): BuildStatus {
fun initStatus(params: Map<String, Any>, finallyStage: Boolean): BuildStatus {
return if (params[SkipElementUtils.getSkipElementVariableName(id!!)] == "true") { // 参数中指明要求跳过
BuildStatus.SKIP // 跳过
} else if (!isElementEnable()) { // 插件未启用
BuildStatus.SKIP // 跳过
} else if (finallyStage) { // 除以上指定跳过或不启用的以外,在final Stage 下的插件都需要重置状态
BuildStatus.QUEUE
} else if (status == BuildStatus.SKIP.name) { // 原本状态为SKIP,一般为 Rebuild/Fail Retry 的上一次执行标志下来
BuildStatus.SKIP // 跳过
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,12 @@ import com.tencent.devops.common.pipeline.pojo.element.trigger.RemoteTriggerElem
*
* @version 1.0
*/
@Suppress("ALL")
object ModelUtils {

/**
* 初始化旧的数据
* 兼容旧的数据结构
*/
@Suppress("ALL")
fun initContainerOldData(c: Container) {
if (c is NormalContainer) {
if (c.jobControlOption == null) {
Expand Down Expand Up @@ -98,50 +98,44 @@ object ModelUtils {
return false
}

fun refreshCanRetry(model: Model, canRetry: Boolean, status: BuildStatus) {
fun refreshCanRetry(model: Model, canRetry: Boolean) {
model.stages.forEach { s ->
s.canRetry = (BuildStatus.parse(s.status).isFailure() ||
BuildStatus.parse(s.status).isCancel()) && canRetry
s.containers.forEach { c ->
initContainerOldData(c)
if (c is VMBuildContainer) {
c.canRetry = c.canRetry ?: false && canRetry
c.canRetry = (c.canRetry ?: false) && canRetry
}

val failElements = mutableListOf<Element>()
c.elements.forEach { e ->
refreshElement(c, e, canRetry, failElements, status)
refreshElement(element = e, canRetry = canRetry, failElements = failElements)
}
}
}
}

private fun refreshElement(
c: Container,
e: Element,
canRetry: Boolean,
failElements: MutableList<Element>,
status: BuildStatus
) {
if (c is VMBuildContainer) {
e.canRetry = e.canRetry ?: false && canRetry
} else { // 目前暂时不放开无构建环境的即时重试,要重新设计重试的方式。
e.canRetry = e.canRetry ?: false && status.isFailure()
}
val additionalOptions = e.additionalOptions
private fun refreshElement(element: Element, canRetry: Boolean, failElements: MutableList<Element>) {

element.canRetry = (element.canRetry ?: false) && canRetry

val additionalOptions = element.additionalOptions
if (additionalOptions != null && additionalOptions.enable) {
if (additionalOptions.continueWhenFailed) {
e.canRetry = false
element.canRetry = false
} else if (additionalOptions.runCondition == RunCondition.PRE_TASK_FAILED_BUT_CANCEL ||
additionalOptions.runCondition == RunCondition.PRE_TASK_FAILED_ONLY
) {
// 前面有失败的插件时也要运行的插件,将前面的失败插件置为不可重试
e.canRetry = false
element.canRetry = false
failElements.forEach {
it.canRetry = false
}
}
}
if (e.canRetry == true) { // 先记录可重试的执行失败插件
failElements.add(e)
if (element.canRetry == true) { // 先记录可重试的执行失败插件
failElements.add(element)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,27 +50,37 @@ class ElementTest {
val element = ManualTriggerElement(id = "1")
element.status = BuildStatus.QUEUE.name
val skipElementVariableName = SkipElementUtils.getSkipElementVariableName(element.id!!)
var takeStatus = element.initStatus(params = mapOf(skipElementVariableName to "true"))
var finallyStage = true
var takeStatus = element.initStatus(mapOf(skipElementVariableName to "true"), finallyStage = finallyStage)
assertEquals(BuildStatus.SKIP.name, takeStatus.name)

element.status = BuildStatus.SUCCEED.name
takeStatus = element.initStatus(params = mapOf(skipElementVariableName to "false"))
finallyStage = true
takeStatus = element.initStatus(params = mapOf(skipElementVariableName to "false"), finallyStage = finallyStage)
assertEquals(BuildStatus.QUEUE.name, takeStatus.name)

element.status = BuildStatus.FAILED.name
takeStatus = element.initStatus(params = mapOf(skipElementVariableName to "false"))
finallyStage = false
takeStatus = element.initStatus(params = mapOf(skipElementVariableName to "false"), finallyStage = finallyStage)
assertEquals(BuildStatus.QUEUE.name, takeStatus.name)

element.status = BuildStatus.QUEUE.name
takeStatus = element.initStatus(params = mapOf(skipElementVariableName to "false"))
finallyStage = false
takeStatus = element.initStatus(params = mapOf(skipElementVariableName to "false"), finallyStage = finallyStage)
assertEquals(BuildStatus.QUEUE.name, takeStatus.name)

element.status = BuildStatus.SKIP.name
takeStatus = element.initStatus(params = mapOf(skipElementVariableName to "false"))
finallyStage = false
takeStatus = element.initStatus(params = mapOf(skipElementVariableName to "false"), finallyStage = finallyStage)
assertEquals(BuildStatus.SKIP.name, takeStatus.name)

element.status = BuildStatus.SKIP.name
finallyStage = true
takeStatus = element.initStatus(params = mapOf(skipElementVariableName to "false"), finallyStage = finallyStage)
assertEquals(BuildStatus.QUEUE.name, takeStatus.name)

element.additionalOptions = elementAdditionalOptions(enable = false)
takeStatus = element.initStatus(params = mapOf(skipElementVariableName to "false"))
takeStatus = element.initStatus(params = mapOf(skipElementVariableName to "false"), finallyStage = finallyStage)
assertEquals(BuildStatus.SKIP.name, takeStatus.name)
}

Expand Down Expand Up @@ -172,4 +182,11 @@ class ElementTest {
subscriptionPauseUser = ""
)
}

@Test
fun genTaskParams() {
val element = ManualTriggerElement(id = "1")
element.cleanUp()
assertEquals("1", element.genTaskParams()["id"])
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -135,37 +135,47 @@ class ModelUtilsTest {
val containers = mutableListOf<Container>()
val stages = mutableListOf<Stage>()
val model = Model(name = "test", desc = "description", stages = stages)
stages.add(Stage(containers = containers, id = "1"))
stages.add(Stage(containers = containers, id = "1", status = BuildStatus.CANCELED.name))
val noRetryElement = ManualReviewUserTaskElement()
containers.add(NormalContainer(elements = listOf(noRetryElement)))
ModelUtils.refreshCanRetry(model = model, canRetry = true, status = BuildStatus.FAILED)
ModelUtils.refreshCanRetry(model = model, canRetry = true)
assertFalse(noRetryElement.canRetry!!)
assertTrue(stages[0].canRetry!!)

noRetryElement.additionalOptions = elementAdditionalOptions(enable = true)
ModelUtils.refreshCanRetry(model = model, canRetry = true, status = BuildStatus.FAILED)
stages[0].status = BuildStatus.SUCCEED.name
ModelUtils.refreshCanRetry(model = model, canRetry = true)
assertFalse(noRetryElement.canRetry!!)
assertFalse(stages[0].canRetry!!)

// 状态是成功的 则不允许 重试
noRetryElement.additionalOptions = elementAdditionalOptions(enable = true)
ModelUtils.refreshCanRetry(model = model, canRetry = true, status = BuildStatus.SUCCEED)
ModelUtils.refreshCanRetry(model = model, canRetry = true)
assertFalse(noRetryElement.canRetry!!)

val retryElement = LinuxScriptElement(script = "pwd",
scriptType = BuildScriptType.SHELL,
continueNoneZero = false)
val elements = mutableListOf(retryElement)
containers.add(VMBuildContainer(baseOS = VMBaseOS.MACOS, elements = elements))
ModelUtils.refreshCanRetry(model = model, canRetry = true, status = BuildStatus.FAILED)
ModelUtils.refreshCanRetry(model = model, canRetry = true)
assertFalse(noRetryElement.canRetry!!)

retryElement.canRetry = true
ModelUtils.refreshCanRetry(model = model, canRetry = true, status = BuildStatus.FAILED)
ModelUtils.refreshCanRetry(model = model, canRetry = true)
assertTrue(retryElement.canRetry!!)

// 默认允许重试
retryElement.additionalOptions = elementAdditionalOptions(enable = true)
ModelUtils.refreshCanRetry(model = model, canRetry = true, status = BuildStatus.FAILED)
stages[0].status = BuildStatus.FAILED.name
ModelUtils.refreshCanRetry(model = model, canRetry = true)
assertTrue(retryElement.canRetry!!)
assertTrue(stages[0].canRetry!!)
// 不允许重试
stages[0].status = BuildStatus.FAILED.name
ModelUtils.refreshCanRetry(model = model, canRetry = false)
assertFalse(retryElement.canRetry!!)
assertFalse(stages[0].canRetry!!)

val preTaskFailedRun = LinuxScriptElement(script = "cd ..",
scriptType = BuildScriptType.SHELL,
Expand All @@ -175,7 +185,7 @@ class ModelUtilsTest {
continueWhenFailed = false)
elements.add(preTaskFailedRun)
// 通过前面插件即使失败也运行,让前置失败的插件不能重试
ModelUtils.refreshCanRetry(model = model, canRetry = true, status = BuildStatus.FAILED)
ModelUtils.refreshCanRetry(model = model, canRetry = true)
assertFalse(retryElement.canRetry!!)
assertFalse(preTaskFailedRun.canRetry!!)

Expand All @@ -184,7 +194,11 @@ class ModelUtilsTest {
runCondition = RunCondition.PRE_TASK_FAILED_BUT_CANCEL,
continueWhenFailed = true)

ModelUtils.refreshCanRetry(model = model, canRetry = true, status = BuildStatus.FAILED)
ModelUtils.refreshCanRetry(model = model, canRetry = true)
assertFalse(retryElement.canRetry!!)
assertFalse(preTaskFailedRun.canRetry!!)

ModelUtils.refreshCanRetry(model = model, canRetry = false)
assertFalse(retryElement.canRetry!!)
assertFalse(preTaskFailedRun.canRetry!!)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ import javax.ws.rs.core.MediaType
@Consumes(MediaType.APPLICATION_JSON)
interface BuildBuildResource {

@Deprecated("replace by EngineBuildResource")
@Deprecated("replace by BuildJobResource")
@ApiOperation("构建机器启动成功")
@PUT
@Path("/started")
Expand All @@ -76,7 +76,7 @@ interface BuildBuildResource {
vmName: String
): Result<BuildVariables>

@Deprecated("replace by EngineBuildResource")
@Deprecated("replace by BuildJobResource")
@ApiOperation("构建机请求任务")
@GET
@Path("/claim")
Expand All @@ -92,7 +92,7 @@ interface BuildBuildResource {
vmName: String
): Result<BuildTask>

@Deprecated("replace by EngineBuildResource")
@Deprecated("replace by BuildJobResource")
@ApiOperation("构建机完成任务")
@POST
@Path("/complete")
Expand All @@ -110,7 +110,7 @@ interface BuildBuildResource {
result: BuildTaskResult
): Result<Boolean>

@Deprecated("replace by EngineBuildResource")
@Deprecated("replace by BuildJobResource")
@ApiOperation("End the seq build")
@POST
@Path("/end")
Expand All @@ -126,7 +126,7 @@ interface BuildBuildResource {
vmName: String
): Result<Boolean>

@Deprecated("replace by EngineBuildResource")
@Deprecated("replace by BuildJobResource")
@ApiOperation("timeout & end the seq build")
@POST
@Path("/timeout")
Expand All @@ -145,7 +145,7 @@ interface BuildBuildResource {
vmSeqId: String
): Result<Boolean>

@Deprecated("replace by EngineBuildResource")
@Deprecated("replace by BuildJobResource")
@ApiOperation("Heartbeat")
@POST
@Path("/heartbeat")
Expand Down
Loading

0 comments on commit 1199c75

Please sign in to comment.