From 3d9eaf54f8a1464d64603a346d1235eb7dc6d579 Mon Sep 17 00:00:00 2001 From: nisanharamati Date: Fri, 4 Oct 2019 12:35:17 -0700 Subject: [PATCH] feat(Kayenta): Support canaryConfigName as canary identifier (#3215) - Allow canaries to be specified via canaryConfigName, in addition to canaryConfigId. - canaryConfigId is still the default, and if it is provided, no additional work is necessary. - if canaryConfigId is null, then if a canaryConfigName is provided, it can be used, together with the currentApplication name, to resolve a valid canaryConfigId using the Kayenta `/canaryConfig` end point. - Once a list of candidate canaryConfigIds is produced, an additional check is performed to ensure only one candidate was returned. If no candidates were found, an error is thrown, and likewise if more than one candidate matches on both canaryConfigName and the current application name, an error is thrown. --- .../spinnaker/orca/kayenta/KayentaService.kt | 11 ++++++ .../kayenta/model/KayentaCanaryContext.kt | 3 +- .../orca/kayenta/model/RunCanaryContext.kt | 3 +- .../pipeline/RunCanaryIntervalsStage.kt | 1 + .../pipeline/RunCanaryPipelineStage.kt | 10 ++++++ .../tasks/ResolveKayentaConfigIdTask.kt | 34 +++++++++++++++++++ .../kayenta/tasks/RunKayentaCanaryTask.kt | 4 +++ 7 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 orca-kayenta/src/main/kotlin/com/netflix/spinnaker/orca/kayenta/tasks/ResolveKayentaConfigIdTask.kt diff --git a/orca-kayenta/src/main/kotlin/com/netflix/spinnaker/orca/kayenta/KayentaService.kt b/orca-kayenta/src/main/kotlin/com/netflix/spinnaker/orca/kayenta/KayentaService.kt index 2bc87740e4..0b4ec23a0c 100644 --- a/orca-kayenta/src/main/kotlin/com/netflix/spinnaker/orca/kayenta/KayentaService.kt +++ b/orca-kayenta/src/main/kotlin/com/netflix/spinnaker/orca/kayenta/KayentaService.kt @@ -54,6 +54,9 @@ interface KayentaService { @GET("/credentials") fun getCredentials(): List + + @GET("/canaryConfig") + fun getAllCanaryConfigs(): List } data class CanaryExecutionRequest( @@ -130,3 +133,11 @@ data class KayentaCredential( val name: String, val type: String ) + +data class KayentaCanaryConfig( + val id: String, + val name: String, + val updatedTimestamp: Long?, + val updateTimestampIso: String?, + val applications: List +) diff --git a/orca-kayenta/src/main/kotlin/com/netflix/spinnaker/orca/kayenta/model/KayentaCanaryContext.kt b/orca-kayenta/src/main/kotlin/com/netflix/spinnaker/orca/kayenta/model/KayentaCanaryContext.kt index 51574c200a..7a112121b2 100644 --- a/orca-kayenta/src/main/kotlin/com/netflix/spinnaker/orca/kayenta/model/KayentaCanaryContext.kt +++ b/orca-kayenta/src/main/kotlin/com/netflix/spinnaker/orca/kayenta/model/KayentaCanaryContext.kt @@ -26,7 +26,8 @@ data class KayentaCanaryContext( val metricsAccountName: String? = null, val configurationAccountName: String? = null, val storageAccountName: String? = null, - val canaryConfigId: String, + val canaryConfigId: String? = null, + val canaryConfigName: String? = null, val scopes: List = emptyList(), val scoreThresholds: Thresholds = Thresholds(pass = 75, marginal = 50), val siteLocal: Map = emptyMap(), diff --git a/orca-kayenta/src/main/kotlin/com/netflix/spinnaker/orca/kayenta/model/RunCanaryContext.kt b/orca-kayenta/src/main/kotlin/com/netflix/spinnaker/orca/kayenta/model/RunCanaryContext.kt index 0e24ea157a..76945bc8d1 100644 --- a/orca-kayenta/src/main/kotlin/com/netflix/spinnaker/orca/kayenta/model/RunCanaryContext.kt +++ b/orca-kayenta/src/main/kotlin/com/netflix/spinnaker/orca/kayenta/model/RunCanaryContext.kt @@ -24,7 +24,8 @@ internal data class RunCanaryContext( val metricsAccountName: String?, val configurationAccountName: String?, val storageAccountName: String?, - val canaryConfigId: String, + val canaryConfigId: String?, + val canaryConfigName: String?, val scopes: Map = emptyMap(), val scoreThresholds: Thresholds, val siteLocal: Map = emptyMap() diff --git a/orca-kayenta/src/main/kotlin/com/netflix/spinnaker/orca/kayenta/pipeline/RunCanaryIntervalsStage.kt b/orca-kayenta/src/main/kotlin/com/netflix/spinnaker/orca/kayenta/pipeline/RunCanaryIntervalsStage.kt index 3272952720..3e8eae2a71 100644 --- a/orca-kayenta/src/main/kotlin/com/netflix/spinnaker/orca/kayenta/pipeline/RunCanaryIntervalsStage.kt +++ b/orca-kayenta/src/main/kotlin/com/netflix/spinnaker/orca/kayenta/pipeline/RunCanaryIntervalsStage.kt @@ -102,6 +102,7 @@ class RunCanaryIntervalsStage(private val clock: Clock) : StageDefinitionBuilder canaryConfig.configurationAccountName, canaryConfig.storageAccountName, canaryConfig.canaryConfigId, + canaryConfig.canaryConfigName, buildRequestScopes(canaryConfig, getDeployDetails(parent), i, canaryAnalysisInterval), canaryConfig.scoreThresholds, canaryConfig.siteLocal diff --git a/orca-kayenta/src/main/kotlin/com/netflix/spinnaker/orca/kayenta/pipeline/RunCanaryPipelineStage.kt b/orca-kayenta/src/main/kotlin/com/netflix/spinnaker/orca/kayenta/pipeline/RunCanaryPipelineStage.kt index 622326afb2..3fbd4d2aa2 100644 --- a/orca-kayenta/src/main/kotlin/com/netflix/spinnaker/orca/kayenta/pipeline/RunCanaryPipelineStage.kt +++ b/orca-kayenta/src/main/kotlin/com/netflix/spinnaker/orca/kayenta/pipeline/RunCanaryPipelineStage.kt @@ -17,10 +17,13 @@ package com.netflix.spinnaker.orca.kayenta.pipeline import com.netflix.spinnaker.orca.CancellableStage +import com.netflix.spinnaker.orca.ext.mapTo import com.netflix.spinnaker.orca.ext.withTask import com.netflix.spinnaker.orca.kayenta.KayentaService +import com.netflix.spinnaker.orca.kayenta.model.RunCanaryContext import com.netflix.spinnaker.orca.kayenta.tasks.MonitorKayentaCanaryTask import com.netflix.spinnaker.orca.kayenta.tasks.RunKayentaCanaryTask +import com.netflix.spinnaker.orca.kayenta.tasks.ResolveKayentaConfigIdTask import com.netflix.spinnaker.orca.pipeline.StageDefinitionBuilder import com.netflix.spinnaker.orca.pipeline.TaskNode import com.netflix.spinnaker.orca.pipeline.model.Stage @@ -37,6 +40,13 @@ class RunCanaryPipelineStage( private val log = LoggerFactory.getLogger(javaClass) override fun taskGraph(stage: Stage, builder: TaskNode.Builder) { + val context = stage.mapTo() + if (context.canaryConfigId.isNullOrEmpty()) { + if (context.canaryConfigName.isNullOrEmpty()) { + throw IllegalArgumentException("Canary config must be specified as either UUID or name string") + } + builder.withTask("resolveKayentaConfigId") + } builder .withTask("runCanary") .withTask("monitorCanary") diff --git a/orca-kayenta/src/main/kotlin/com/netflix/spinnaker/orca/kayenta/tasks/ResolveKayentaConfigIdTask.kt b/orca-kayenta/src/main/kotlin/com/netflix/spinnaker/orca/kayenta/tasks/ResolveKayentaConfigIdTask.kt new file mode 100644 index 0000000000..d471220496 --- /dev/null +++ b/orca-kayenta/src/main/kotlin/com/netflix/spinnaker/orca/kayenta/tasks/ResolveKayentaConfigIdTask.kt @@ -0,0 +1,34 @@ +package com.netflix.spinnaker.orca.kayenta.tasks + +import com.netflix.spinnaker.orca.ExecutionStatus.SUCCEEDED +import com.netflix.spinnaker.orca.Task +import com.netflix.spinnaker.orca.TaskResult +import com.netflix.spinnaker.orca.ext.mapTo +import com.netflix.spinnaker.orca.kayenta.KayentaService +import com.netflix.spinnaker.orca.pipeline.model.Stage +import org.slf4j.LoggerFactory +import org.springframework.stereotype.Component + +@Component +class ResolveKayentaConfigIdTask( + private val kayentaService: KayentaService +) : Task { + + private val log = LoggerFactory.getLogger(javaClass) + + override fun execute(stage: Stage): TaskResult { + val configName = stage.mapTo("/canaryConfigName") + val currentApplication = stage.execution.application + val canaryConfigList = kayentaService.getAllCanaryConfigs() + val candidates = canaryConfigList.asSequence() + .filter { it.name == configName && it.applications.contains(currentApplication) } + .toList() + + if (candidates.size == 0) { + throw NoSuchElementException("Couldn't find a configId for configName $configName and application $currentApplication") + } else if (candidates.size > 1) { + throw IllegalArgumentException("Found more than one configId for configName $configName and application $currentApplication") + } + return TaskResult.builder(SUCCEEDED).context("canaryConfigId", candidates[0].id).build() + } +} diff --git a/orca-kayenta/src/main/kotlin/com/netflix/spinnaker/orca/kayenta/tasks/RunKayentaCanaryTask.kt b/orca-kayenta/src/main/kotlin/com/netflix/spinnaker/orca/kayenta/tasks/RunKayentaCanaryTask.kt index 318739affb..19b334f468 100644 --- a/orca-kayenta/src/main/kotlin/com/netflix/spinnaker/orca/kayenta/tasks/RunKayentaCanaryTask.kt +++ b/orca-kayenta/src/main/kotlin/com/netflix/spinnaker/orca/kayenta/tasks/RunKayentaCanaryTask.kt @@ -37,6 +37,10 @@ class RunKayentaCanaryTask( override fun execute(stage: Stage): TaskResult { val context = stage.mapTo() + if (context.canaryConfigId.isNullOrEmpty()) { + throw IllegalArgumentException("Invalid canaryConfigId. Was the correct configName or configId used?") + } + val canaryPipelineExecutionId = kayentaService.create( context.canaryConfigId, stage.execution.application,