From f3e3db07f497f96e8dcf3f6323a0c8b01a4ec89f Mon Sep 17 00:00:00 2001 From: Asher Feldman Date: Mon, 16 Sep 2019 14:23:52 -0700 Subject: [PATCH] chore(core): removing clusterLock feature (#3136) --- ...tClusterWideClouddriverOperationStage.java | 55 ----- .../pipeline/cluster/DisableClusterStage.java | 9 +- .../cluster/RollbackClusterStage.java | 47 +--- .../cluster/ScaleDownClusterStage.java | 9 +- .../pipeline/cluster/ShrinkClusterStage.java | 9 +- .../AbstractDeployStrategyStage.groovy | 33 +-- ...TargetServerGroupLinearStageSupport.groovy | 39 +--- ...erWideClouddriverOperationStageSpec.groovy | 104 --------- .../cluster/RollbackClusterStageSpec.groovy | 74 +----- .../CreateServerGroupStageSpec.groovy | 4 +- .../strategies/HighlanderStrategySpec.groovy | 6 +- .../strategies/RedBlackStrategySpec.groovy | 8 +- .../RollingRedBlackStrategySpec.groovy | 4 +- ...etServerGroupLinearStageSupportSpec.groovy | 3 - ...oudFoundryDeployStagePreProcessorTest.java | 2 +- ...xecutionCompleteLockReleasingListener.java | 84 ------- .../spinnaker/orca/locks/LockContext.java | 181 --------------- ...LockExtendingTaskExecutionInterceptor.java | 131 ----------- .../orca/locks/LockFailureException.java | 52 ----- .../spinnaker/orca/locks/LockManager.java | 148 ------------ .../locks/LockingConfigurationProperties.java | 69 ------ .../orca/pipeline/AcquireLockStage.java | 43 ---- .../orca/pipeline/ReleaseLockStage.java | 40 ---- .../orca/pipeline/tasks/AcquireLockTask.java | 86 ------- .../pipeline/tasks/DetermineLockTask.java | 99 -------- .../orca/pipeline/tasks/ReleaseLockTask.java | 46 ---- .../orca/locks/LockContextSpec.groovy | 122 ---------- .../pipeline/tasks/AcquireLockTaskSpec.groovy | 124 ---------- .../tasks/DetermineLockTaskSpec.groovy | 147 ------------ .../orca/locks/AbstractRedisLockManager.java | 215 ------------------ .../orca/locks/RedisClusterLockManager.java | 53 ----- .../orca/locks/RedisLockManager.java | 90 -------- .../orca/locks/RedisLockManagerSpec.groovy | 203 ----------------- .../com/netflix/spinnaker/orca/MainSpec.java | 3 - 34 files changed, 20 insertions(+), 2322 deletions(-) delete mode 100644 orca-clouddriver/src/test/groovy/com/netflix/spinnaker/orca/clouddriver/pipeline/cluster/AbstractClusterWideClouddriverOperationStageSpec.groovy delete mode 100644 orca-core/src/main/java/com/netflix/spinnaker/orca/locks/ExecutionCompleteLockReleasingListener.java delete mode 100644 orca-core/src/main/java/com/netflix/spinnaker/orca/locks/LockContext.java delete mode 100644 orca-core/src/main/java/com/netflix/spinnaker/orca/locks/LockExtendingTaskExecutionInterceptor.java delete mode 100644 orca-core/src/main/java/com/netflix/spinnaker/orca/locks/LockFailureException.java delete mode 100644 orca-core/src/main/java/com/netflix/spinnaker/orca/locks/LockManager.java delete mode 100644 orca-core/src/main/java/com/netflix/spinnaker/orca/locks/LockingConfigurationProperties.java delete mode 100644 orca-core/src/main/java/com/netflix/spinnaker/orca/pipeline/AcquireLockStage.java delete mode 100644 orca-core/src/main/java/com/netflix/spinnaker/orca/pipeline/ReleaseLockStage.java delete mode 100644 orca-core/src/main/java/com/netflix/spinnaker/orca/pipeline/tasks/AcquireLockTask.java delete mode 100644 orca-core/src/main/java/com/netflix/spinnaker/orca/pipeline/tasks/DetermineLockTask.java delete mode 100644 orca-core/src/main/java/com/netflix/spinnaker/orca/pipeline/tasks/ReleaseLockTask.java delete mode 100644 orca-core/src/test/groovy/com/netflix/spinnaker/orca/locks/LockContextSpec.groovy delete mode 100644 orca-core/src/test/groovy/com/netflix/spinnaker/orca/pipeline/tasks/AcquireLockTaskSpec.groovy delete mode 100644 orca-core/src/test/groovy/com/netflix/spinnaker/orca/pipeline/tasks/DetermineLockTaskSpec.groovy delete mode 100644 orca-redis/src/main/java/com/netflix/spinnaker/orca/locks/AbstractRedisLockManager.java delete mode 100644 orca-redis/src/main/java/com/netflix/spinnaker/orca/locks/RedisClusterLockManager.java delete mode 100644 orca-redis/src/main/java/com/netflix/spinnaker/orca/locks/RedisLockManager.java delete mode 100644 orca-redis/src/test/groovy/com/netflix/spinnaker/orca/locks/RedisLockManagerSpec.groovy diff --git a/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/pipeline/cluster/AbstractClusterWideClouddriverOperationStage.java b/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/pipeline/cluster/AbstractClusterWideClouddriverOperationStage.java index f8c101a5d7..d92d3f2cae 100644 --- a/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/pipeline/cluster/AbstractClusterWideClouddriverOperationStage.java +++ b/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/pipeline/cluster/AbstractClusterWideClouddriverOperationStage.java @@ -27,12 +27,7 @@ import com.netflix.spinnaker.orca.clouddriver.tasks.cluster.AbstractClusterWideClouddriverTask; import com.netflix.spinnaker.orca.clouddriver.tasks.cluster.AbstractWaitForClusterWideClouddriverTask; import com.netflix.spinnaker.orca.clouddriver.tasks.servergroup.ServerGroupCacheForceRefreshTask; -import com.netflix.spinnaker.orca.clouddriver.utils.ClusterLockHelper; import com.netflix.spinnaker.orca.clouddriver.utils.MonikerHelper; -import com.netflix.spinnaker.orca.clouddriver.utils.TrafficGuard; -import com.netflix.spinnaker.orca.locks.LockingConfigurationProperties; -import com.netflix.spinnaker.orca.pipeline.AcquireLockStage; -import com.netflix.spinnaker.orca.pipeline.ReleaseLockStage; import com.netflix.spinnaker.orca.pipeline.StageDefinitionBuilder; import com.netflix.spinnaker.orca.pipeline.TaskNode; import com.netflix.spinnaker.orca.pipeline.graph.StageGraphBuilder; @@ -45,16 +40,10 @@ public abstract class AbstractClusterWideClouddriverOperationStage implements StageDefinitionBuilder { - private final TrafficGuard trafficGuard; - private final LockingConfigurationProperties lockingConfigurationProperties; private final DynamicConfigService dynamicConfigService; protected AbstractClusterWideClouddriverOperationStage( - TrafficGuard trafficGuard, - LockingConfigurationProperties lockingConfigurationProperties, DynamicConfigService dynamicConfigService) { - this.trafficGuard = Objects.requireNonNull(trafficGuard); - this.lockingConfigurationProperties = Objects.requireNonNull(lockingConfigurationProperties); this.dynamicConfigService = dynamicConfigService; } @@ -69,53 +58,9 @@ protected static String getStepName(String taskClassSimpleName) { return taskClassSimpleName; } - @Override - public final void beforeStages(@Nonnull Stage parent, @Nonnull StageGraphBuilder graph) { - if (lockingConfigurationProperties.isEnabled()) { - List locations = locationsFromStage(parent.getContext()); - ClusterSelection clusterSelection = parent.mapTo(ClusterSelection.class); - for (Location location : locations) { - String lockName = - ClusterLockHelper.clusterLockName( - clusterSelection.getMoniker(), clusterSelection.getCredentials(), location); - if (trafficGuard.hasDisableLock( - clusterSelection.getMoniker(), clusterSelection.getCredentials(), location)) { - graph.add( - stage -> { - stage.setType(AcquireLockStage.PIPELINE_TYPE); - stage.getContext().put("lock", Collections.singletonMap("lockName", lockName)); - }); - } - } - } - addAdditionalBeforeStages(parent, graph); - } - protected void addAdditionalBeforeStages( @Nonnull Stage parent, @Nonnull StageGraphBuilder graph) {} - @Override - public final void afterStages(@Nonnull Stage parent, @Nonnull StageGraphBuilder graph) { - addAdditionalAfterStages(parent, graph); - if (lockingConfigurationProperties.isEnabled()) { - List locations = locationsFromStage(parent.getContext()); - ClusterSelection clusterSelection = parent.mapTo(ClusterSelection.class); - for (Location location : locations) { - String lockName = - ClusterLockHelper.clusterLockName( - clusterSelection.getMoniker(), clusterSelection.getCredentials(), location); - if (trafficGuard.hasDisableLock( - clusterSelection.getMoniker(), clusterSelection.getCredentials(), location)) { - graph.append( - stage -> { - stage.setType(ReleaseLockStage.PIPELINE_TYPE); - stage.getContext().put("lock", Collections.singletonMap("lockName", lockName)); - }); - } - } - } - } - protected void addAdditionalAfterStages( @Nonnull Stage parent, @Nonnull StageGraphBuilder graph) {} diff --git a/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/pipeline/cluster/DisableClusterStage.java b/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/pipeline/cluster/DisableClusterStage.java index 5ca132f022..4d417e5d96 100644 --- a/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/pipeline/cluster/DisableClusterStage.java +++ b/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/pipeline/cluster/DisableClusterStage.java @@ -21,8 +21,6 @@ import com.netflix.spinnaker.orca.clouddriver.tasks.cluster.AbstractWaitForClusterWideClouddriverTask; import com.netflix.spinnaker.orca.clouddriver.tasks.cluster.DisableClusterTask; import com.netflix.spinnaker.orca.clouddriver.tasks.cluster.WaitForClusterDisableTask; -import com.netflix.spinnaker.orca.clouddriver.utils.TrafficGuard; -import com.netflix.spinnaker.orca.locks.LockingConfigurationProperties; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -32,11 +30,8 @@ public class DisableClusterStage extends AbstractClusterWideClouddriverOperation public static final String STAGE_TYPE = "disableCluster"; @Autowired - public DisableClusterStage( - TrafficGuard trafficGuard, - LockingConfigurationProperties lockingConfigurationProperties, - DynamicConfigService dynamicConfigService) { - super(trafficGuard, lockingConfigurationProperties, dynamicConfigService); + public DisableClusterStage(DynamicConfigService dynamicConfigService) { + super(dynamicConfigService); } @Override diff --git a/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/pipeline/cluster/RollbackClusterStage.java b/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/pipeline/cluster/RollbackClusterStage.java index e282596b05..e726a74494 100644 --- a/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/pipeline/cluster/RollbackClusterStage.java +++ b/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/pipeline/cluster/RollbackClusterStage.java @@ -16,14 +16,11 @@ package com.netflix.spinnaker.orca.clouddriver.pipeline.cluster; -import com.netflix.spinnaker.moniker.Moniker; import com.netflix.spinnaker.orca.clouddriver.pipeline.servergroup.RollbackServerGroupStage; -import com.netflix.spinnaker.orca.clouddriver.pipeline.servergroup.support.Location; import com.netflix.spinnaker.orca.clouddriver.tasks.cluster.DetermineRollbackCandidatesTask; -import com.netflix.spinnaker.orca.clouddriver.utils.ClusterLockHelper; -import com.netflix.spinnaker.orca.clouddriver.utils.TrafficGuard; -import com.netflix.spinnaker.orca.locks.LockingConfigurationProperties; -import com.netflix.spinnaker.orca.pipeline.*; +import com.netflix.spinnaker.orca.pipeline.StageDefinitionBuilder; +import com.netflix.spinnaker.orca.pipeline.TaskNode; +import com.netflix.spinnaker.orca.pipeline.WaitStage; import com.netflix.spinnaker.orca.pipeline.graph.StageGraphBuilder; import com.netflix.spinnaker.orca.pipeline.model.Stage; import java.util.Collections; @@ -32,23 +29,12 @@ import java.util.Map; import java.util.stream.Collectors; import javax.annotation.Nonnull; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class RollbackClusterStage implements StageDefinitionBuilder { public static final String PIPELINE_CONFIG_TYPE = "rollbackCluster"; - private final TrafficGuard trafficGuard; - private final LockingConfigurationProperties lockingConfigurationProperties; - - @Autowired - public RollbackClusterStage( - TrafficGuard trafficGuard, LockingConfigurationProperties lockingConfigurationProperties) { - this.trafficGuard = trafficGuard; - this.lockingConfigurationProperties = lockingConfigurationProperties; - } - @Override public void taskGraph(@Nonnull Stage stage, @Nonnull TaskNode.Builder builder) { builder.withTask("determineRollbackCandidates", DetermineRollbackCandidatesTask.class); @@ -68,24 +54,6 @@ public void afterStages(@Nonnull Stage parent, @Nonnull StageGraphBuilder graph) stageData.regions.stream().filter(rollbackTypes::containsKey).collect(Collectors.toList()); for (String region : regionsToRollback) { - boolean addLocking = false; - String lockName; - if (lockingConfigurationProperties.isEnabled()) { - final Location location = Location.region(region); - addLocking = - trafficGuard.hasDisableLock(stageData.moniker, stageData.credentials, location); - lockName = - ClusterLockHelper.clusterLockName(stageData.moniker, stageData.credentials, location); - if (addLocking) { - graph.append( - stage -> { - stage.setType(AcquireLockStage.PIPELINE_TYPE); - stage.getContext().put("lock", Collections.singletonMap("lockName", lockName)); - }); - } - } else { - lockName = null; - } Map context = new HashMap<>(); context.put("rollbackType", ((Map) parent.getOutputs().get("rollbackTypes")).get(region)); @@ -110,14 +78,6 @@ public void afterStages(@Nonnull Stage parent, @Nonnull StageGraphBuilder graph) it.setContext(context); }); - if (addLocking) { - graph.append( - stage -> { - stage.setType(ReleaseLockStage.PIPELINE_TYPE); - stage.getContext().put("lock", Collections.singletonMap("lockName", lockName)); - }); - } - if (stageData.waitTimeBetweenRegions != null && regionsToRollback.indexOf(region) < regionsToRollback.size() - 1) { // only add the waitStage if we're not the very last region! @@ -162,7 +122,6 @@ private static Map propagateParentStageContext(Stage parent) { static class StageData { public String credentials; public String cloudProvider; - public Moniker moniker; public List regions; public Long waitTimeBetweenRegions; diff --git a/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/pipeline/cluster/ScaleDownClusterStage.java b/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/pipeline/cluster/ScaleDownClusterStage.java index acdd75704f..957576d82d 100644 --- a/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/pipeline/cluster/ScaleDownClusterStage.java +++ b/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/pipeline/cluster/ScaleDownClusterStage.java @@ -21,8 +21,6 @@ import com.netflix.spinnaker.orca.clouddriver.tasks.cluster.AbstractWaitForClusterWideClouddriverTask; import com.netflix.spinnaker.orca.clouddriver.tasks.cluster.ScaleDownClusterTask; import com.netflix.spinnaker.orca.clouddriver.tasks.cluster.WaitForScaleDownClusterTask; -import com.netflix.spinnaker.orca.clouddriver.utils.TrafficGuard; -import com.netflix.spinnaker.orca.locks.LockingConfigurationProperties; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -30,11 +28,8 @@ public class ScaleDownClusterStage extends AbstractClusterWideClouddriverOperationStage { @Autowired - public ScaleDownClusterStage( - TrafficGuard trafficGuard, - LockingConfigurationProperties lockingConfigurationProperties, - DynamicConfigService dynamicConfigService) { - super(trafficGuard, lockingConfigurationProperties, dynamicConfigService); + public ScaleDownClusterStage(DynamicConfigService dynamicConfigService) { + super(dynamicConfigService); } @Override diff --git a/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/pipeline/cluster/ShrinkClusterStage.java b/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/pipeline/cluster/ShrinkClusterStage.java index eae68c9d21..68788b3ce9 100644 --- a/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/pipeline/cluster/ShrinkClusterStage.java +++ b/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/pipeline/cluster/ShrinkClusterStage.java @@ -21,8 +21,6 @@ import com.netflix.spinnaker.orca.clouddriver.tasks.cluster.AbstractWaitForClusterWideClouddriverTask; import com.netflix.spinnaker.orca.clouddriver.tasks.cluster.ShrinkClusterTask; import com.netflix.spinnaker.orca.clouddriver.tasks.cluster.WaitForClusterShrinkTask; -import com.netflix.spinnaker.orca.clouddriver.utils.TrafficGuard; -import com.netflix.spinnaker.orca.locks.LockingConfigurationProperties; import com.netflix.spinnaker.orca.pipeline.graph.StageGraphBuilder; import com.netflix.spinnaker.orca.pipeline.model.Stage; import java.util.HashMap; @@ -41,11 +39,8 @@ public class ShrinkClusterStage extends AbstractClusterWideClouddriverOperationS @Autowired public ShrinkClusterStage( - TrafficGuard trafficGuard, - LockingConfigurationProperties lockingConfigurationProperties, - DynamicConfigService dynamicConfigService, - DisableClusterStage disableClusterStage) { - super(trafficGuard, lockingConfigurationProperties, dynamicConfigService); + DynamicConfigService dynamicConfigService, DisableClusterStage disableClusterStage) { + super(dynamicConfigService); this.disableClusterStage = disableClusterStage; } diff --git a/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/pipeline/servergroup/strategies/AbstractDeployStrategyStage.groovy b/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/pipeline/servergroup/strategies/AbstractDeployStrategyStage.groovy index 8fc00340bf..73c9de720b 100644 --- a/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/pipeline/servergroup/strategies/AbstractDeployStrategyStage.groovy +++ b/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/pipeline/servergroup/strategies/AbstractDeployStrategyStage.groovy @@ -19,17 +19,12 @@ package com.netflix.spinnaker.orca.clouddriver.pipeline.servergroup.strategies import com.netflix.spinnaker.moniker.Moniker import com.netflix.spinnaker.orca.clouddriver.pipeline.AbstractCloudProviderAwareStage import com.netflix.spinnaker.orca.clouddriver.pipeline.servergroup.support.Location -import com.netflix.spinnaker.orca.clouddriver.pipeline.servergroup.support.TargetServerGroup import com.netflix.spinnaker.orca.clouddriver.tasks.DetermineHealthProvidersTask -import com.netflix.spinnaker.orca.clouddriver.utils.ClusterLockHelper -import com.netflix.spinnaker.orca.clouddriver.utils.MonikerHelper import com.netflix.spinnaker.orca.clouddriver.utils.TrafficGuard import com.netflix.spinnaker.orca.kato.pipeline.strategy.DetermineSourceServerGroupTask import com.netflix.spinnaker.orca.kato.pipeline.support.StageData import com.netflix.spinnaker.orca.kato.tasks.DiffTask -import com.netflix.spinnaker.orca.locks.LockingConfigurationProperties -import com.netflix.spinnaker.orca.pipeline.AcquireLockStage -import com.netflix.spinnaker.orca.pipeline.ReleaseLockStage + import com.netflix.spinnaker.orca.pipeline.TaskNode import com.netflix.spinnaker.orca.pipeline.graph.StageGraphBuilder import com.netflix.spinnaker.orca.pipeline.model.Stage @@ -59,9 +54,6 @@ abstract class AbstractDeployStrategyStage extends AbstractCloudProviderAwareSta @Autowired TrafficGuard trafficGuard - @Autowired - LockingConfigurationProperties lockingConfigurationProperties - AbstractDeployStrategyStage(String name) { super(name) } @@ -118,19 +110,6 @@ abstract class AbstractDeployStrategyStage extends AbstractCloudProviderAwareSta def preProcessors = deployStagePreProcessors.findAll { it.supports(stage) } def stageData = stage.mapTo(StageData) def stages = [] - boolean addLocking = false - String lockName = null - if (lockingConfigurationProperties.isEnabled()) { - def moniker = stageData.moniker?.cluster ? stageData.moniker : MonikerHelper.friggaToMoniker(stageData.cluster) - def location = TargetServerGroup.Support.locationFromStageData(stageData) - lockName = ClusterLockHelper.clusterLockName(moniker, stageData.account, location) - addLocking = trafficGuard.hasDisableLock(moniker, stageData.account, location) - if (addLocking) { - def lockCtx = [lock: [lockName: lockName]] - def lockStage = newStage(stage.execution, AcquireLockStage.PIPELINE_TYPE, "acquireLock", lockCtx, stage, SyntheticStageOwner.STAGE_BEFORE) - stages << lockStage - } - } stages.addAll(strategy.composeFlow(stage)) preProcessors.each { @@ -159,16 +138,6 @@ abstract class AbstractDeployStrategyStage extends AbstractCloudProviderAwareSta ) } } - if (addLocking) { - stages << newStage( - stage.execution, - ReleaseLockStage.PIPELINE_TYPE, - 'releaseLock', - [lock: [lockName: lockName]], - stage, - SyntheticStageOwner.STAGE_AFTER - ) - } return stages } diff --git a/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/pipeline/servergroup/support/TargetServerGroupLinearStageSupport.groovy b/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/pipeline/servergroup/support/TargetServerGroupLinearStageSupport.groovy index d0a197cadc..02136e3528 100644 --- a/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/pipeline/servergroup/support/TargetServerGroupLinearStageSupport.groovy +++ b/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/pipeline/servergroup/support/TargetServerGroupLinearStageSupport.groovy @@ -19,9 +19,7 @@ package com.netflix.spinnaker.orca.clouddriver.pipeline.servergroup.support import com.netflix.spinnaker.orca.clouddriver.utils.ClusterLockHelper import com.netflix.spinnaker.orca.clouddriver.utils.MonikerHelper import com.netflix.spinnaker.orca.clouddriver.utils.TrafficGuard -import com.netflix.spinnaker.orca.locks.LockingConfigurationProperties -import com.netflix.spinnaker.orca.pipeline.AcquireLockStage -import com.netflix.spinnaker.orca.pipeline.ReleaseLockStage + import groovy.transform.stc.ClosureParams import groovy.transform.stc.SimpleType @@ -44,7 +42,6 @@ abstract class TargetServerGroupLinearStageSupport implements StageDefinitionBui @Autowired TargetServerGroupResolver resolver @Autowired TrafficGuard trafficGuard - @Autowired LockingConfigurationProperties lockingConfigurationProperties /** * Override to supply tasks that individual target stages will run. The top level @@ -118,14 +115,6 @@ abstract class TargetServerGroupLinearStageSupport implements StageDefinitionBui } else if (isTopLevel(parent.parent)) { // a non top level stage operates on a single target and may have its own // synthetic stages - withGuard(parent) { moniker, account, location -> - graph.add { - it.type = AcquireLockStage.PIPELINE_TYPE - it.name = 'acquireLock' - it.context = [lock: [lockName: ClusterLockHelper.clusterLockName(moniker, account, location)]] - } - } - if (isDynamicallyBound(parent)) { preDynamic(parent.context, graph) } else { @@ -134,24 +123,6 @@ abstract class TargetServerGroupLinearStageSupport implements StageDefinitionBui } } - void withGuard(Stage parent, - @ClosureParams( - value = SimpleType, - options = [ - 'com.netflix.spinnaker.moniker.Moniker', - 'java.lang.String', - 'com.netflix.spinnaker.orca.clouddriver.pipeline.servergroup.support.Location' - ]) Closure handleGuard) { - if (lockingConfigurationProperties.isEnabled()) { - def param = TargetServerGroup.Params.fromStage(parent) - def moniker = param.moniker ?: MonikerHelper.friggaToMoniker(param.cluster ?: param.serverGroupName) - def location = param.locations[0] - if (trafficGuard.hasDisableLock(moniker, param.credentials, location)) { - handleGuard.call(moniker, param.credentials, location) - } - } - } - @Override final void afterStages( @Nonnull Stage parent, @@ -167,14 +138,6 @@ abstract class TargetServerGroupLinearStageSupport implements StageDefinitionBui } else { postStatic(parent.context, graph) } - - withGuard(parent) { moniker, account, location -> - graph.append { - it.type = ReleaseLockStage.PIPELINE_TYPE - it.name = 'releaseLock' - it.context = [lock: [lockName: ClusterLockHelper.clusterLockName(moniker, account, location)]] - } - } } } diff --git a/orca-clouddriver/src/test/groovy/com/netflix/spinnaker/orca/clouddriver/pipeline/cluster/AbstractClusterWideClouddriverOperationStageSpec.groovy b/orca-clouddriver/src/test/groovy/com/netflix/spinnaker/orca/clouddriver/pipeline/cluster/AbstractClusterWideClouddriverOperationStageSpec.groovy deleted file mode 100644 index 35c3f64614..0000000000 --- a/orca-clouddriver/src/test/groovy/com/netflix/spinnaker/orca/clouddriver/pipeline/cluster/AbstractClusterWideClouddriverOperationStageSpec.groovy +++ /dev/null @@ -1,104 +0,0 @@ -package com.netflix.spinnaker.orca.clouddriver.pipeline.cluster - -import com.netflix.spinnaker.kork.dynamicconfig.DynamicConfigService -import com.netflix.spinnaker.kork.dynamicconfig.SpringDynamicConfigService -import com.netflix.spinnaker.orca.clouddriver.pipeline.servergroup.support.Location -import com.netflix.spinnaker.orca.clouddriver.pipeline.servergroup.support.TargetServerGroup -import com.netflix.spinnaker.orca.clouddriver.tasks.cluster.AbstractClusterWideClouddriverTask -import com.netflix.spinnaker.orca.clouddriver.tasks.cluster.AbstractWaitForClusterWideClouddriverTask -import com.netflix.spinnaker.orca.clouddriver.utils.ClusterLockHelper -import com.netflix.spinnaker.orca.clouddriver.utils.MonikerHelper -import com.netflix.spinnaker.orca.clouddriver.utils.TrafficGuard -import com.netflix.spinnaker.orca.locks.LockingConfigurationProperties -import com.netflix.spinnaker.orca.pipeline.AcquireLockStage -import com.netflix.spinnaker.orca.pipeline.ReleaseLockStage -import com.netflix.spinnaker.orca.pipeline.graph.StageGraphBuilder -import com.netflix.spinnaker.orca.pipeline.model.Stage -import com.netflix.spinnaker.orca.test.model.ExecutionBuilder -import org.springframework.mock.env.MockEnvironment -import spock.lang.Specification -import spock.lang.Unroll - -class AbstractClusterWideClouddriverOperationStageSpec extends Specification { - - def guard = Mock(TrafficGuard) - def env = new MockEnvironment() - def config = new LockingConfigurationProperties(new SpringDynamicConfigService(environment: env)) - def dynamicConfigService = Mock(DynamicConfigService) - - def stageBuilder = new TestStage(guard, config, dynamicConfigService) - - @Unroll - def "should #desc1 inject #expectedType for #desc2 traffic guard protected cluster"() { - given: - env.setProperty('locking.enabled', 'true') - Stage testStage = ExecutionBuilder.stage { - context = stageContext - } - StageGraphBuilder graph = beforeStages ? StageGraphBuilder.beforeStages(testStage) : StageGraphBuilder.afterStages(testStage) - - when: - beforeStages ? stageBuilder.beforeStages(testStage, graph) : stageBuilder.afterStages(testStage, graph) - Iterator stages = graph.build().iterator() - - then: - 1 * guard.hasDisableLock(moniker, account, location) >> shouldLock - stages.hasNext() == shouldLock - if (shouldLock) { - def lockStage = stages.next() - lockStage.type == expectedType - lockStage.context.lock.lockName == lockName - } - - where: - cluster = 'foo' - region = 'bar' - location = new Location(Location.Type.REGION, region) - account = 'baz' - moniker = MonikerHelper.friggaToMoniker(cluster) - stageContext = [ - cluster: cluster, - region: region, - credentials: account - ] - lockName = ClusterLockHelper.clusterLockName(moniker, account, location) - beforeStages << [false, false, true, true] - shouldLock << [false, true, false, true] - expectedType = beforeStages ? AcquireLockStage.PIPELINE_TYPE : ReleaseLockStage.PIPELINE_TYPE - desc1 = shouldLock ? "" : "not" - desc2 = shouldLock ? "" : "non" - } - - - static class TestStage extends AbstractClusterWideClouddriverOperationStage { - TestStage(TrafficGuard trafficGuard, - LockingConfigurationProperties config, - DynamicConfigService dynamicConfigService) { - super(trafficGuard, config, dynamicConfigService) - } - - @Override - protected Class getClusterOperationTask() { - return TestTask - } - - @Override - protected Class getWaitForTask() { - return WaitTask - } - } - - static class TestTask extends AbstractClusterWideClouddriverTask { - @Override - String getClouddriverOperation() { - return null - } - } - - static class WaitTask extends AbstractWaitForClusterWideClouddriverTask { - @Override - boolean isServerGroupOperationInProgress(Stage stage, List interestingHealthProviderNames, Optional serverGroup) { - return false - } - } -} diff --git a/orca-clouddriver/src/test/groovy/com/netflix/spinnaker/orca/clouddriver/pipeline/cluster/RollbackClusterStageSpec.groovy b/orca-clouddriver/src/test/groovy/com/netflix/spinnaker/orca/clouddriver/pipeline/cluster/RollbackClusterStageSpec.groovy index 494e15f2e9..4bee27f65f 100644 --- a/orca-clouddriver/src/test/groovy/com/netflix/spinnaker/orca/clouddriver/pipeline/cluster/RollbackClusterStageSpec.groovy +++ b/orca-clouddriver/src/test/groovy/com/netflix/spinnaker/orca/clouddriver/pipeline/cluster/RollbackClusterStageSpec.groovy @@ -20,7 +20,6 @@ import com.netflix.spinnaker.kork.dynamicconfig.SpringDynamicConfigService import com.netflix.spinnaker.orca.clouddriver.pipeline.servergroup.support.Location import com.netflix.spinnaker.orca.clouddriver.utils.MonikerHelper import com.netflix.spinnaker.orca.clouddriver.utils.TrafficGuard -import com.netflix.spinnaker.orca.locks.LockingConfigurationProperties import com.netflix.spinnaker.orca.pipeline.graph.StageGraphBuilder import com.netflix.spinnaker.orca.pipeline.model.Stage import org.springframework.mock.env.MockEnvironment @@ -33,14 +32,9 @@ class RollbackClusterStageSpec extends Specification { def trafficGuard = Mock(TrafficGuard) def env = new MockEnvironment() - def lockingConfig = new LockingConfigurationProperties(new SpringDynamicConfigService(environment: env)) @Subject - def stageBuilder = new RollbackClusterStage(trafficGuard, lockingConfig) - - def setup() { - env.setProperty('locking.enabled', 'true') - } + def stageBuilder = new RollbackClusterStage() def "should not build any aroundStages()"() { expect: @@ -120,70 +114,4 @@ class RollbackClusterStageSpec extends Specification { [sourceServerGroupCapacitySnapshot: null] || [sourceServerGroupCapacitySnapshot: null] // do not care if value is null [sourceServerGroupCapacitySnapshot: [min: 0, max: 10, desired: 5]] || [sourceServerGroupCapacitySnapshot: [min: 0, max: 10, desired: 5]] } - - def "should add locking stages to traffic guarded clusters"() { - def stage = stage { - context = [ - credentials : 'test', - moniker : [ - app: 'foo', - cluster: 'foo-main', - stack: 'main'], - regions : ["us-west-2", "us-east-1"], - waitTimeBetweenRegions: 60, - ] - outputs = [ - rollbackTypes : [ - "us-west-2": "EXPLICIT", - "us-east-1": "PREVIOUS_IMAGE" - ], - rollbackContexts: [ - "us-west-2": ["foo": "bar"], - "us-east-1": ["bar": "baz"] - ] - ] - } - - when: - def afterStages = buildAfterStages(stage) - - then: - 1 * trafficGuard.hasDisableLock(MonikerHelper.friggaToMoniker('foo-main'), 'test', Location.region('us-east-1')) >> true - 1 * trafficGuard.hasDisableLock(MonikerHelper.friggaToMoniker('foo-main'), 'test', Location.region('us-west-2')) >> true - afterStages*.type == ["acquireLock", "rollbackServerGroup", "releaseLock", "wait", "acquireLock", "rollbackServerGroup", "releaseLock"] - afterStages*.context.region == [ null , "us-west-2" , null , null , null , "us-east-1" , null] - } - - def "should only add locking stages to regions with traffic guards"() { - def stage = stage { - context = [ - credentials : 'test', - moniker : [ - app: 'foo', - cluster: 'foo-main', - stack: 'main'], - regions : ["us-west-2", "us-east-1"], - waitTimeBetweenRegions: 60, - ] - outputs = [ - rollbackTypes : [ - "us-west-2": "EXPLICIT", - "us-east-1": "PREVIOUS_IMAGE" - ], - rollbackContexts: [ - "us-west-2": ["foo": "bar"], - "us-east-1": ["bar": "baz"] - ] - ] - } - - when: - def afterStages = buildAfterStages(stage) - - then: - 1 * trafficGuard.hasDisableLock(MonikerHelper.friggaToMoniker('foo-main'), 'test', Location.region('us-west-2')) >> true - 1 * trafficGuard.hasDisableLock(MonikerHelper.friggaToMoniker('foo-main'), 'test', Location.region('us-east-1')) >> false - afterStages*.type == ["acquireLock", "rollbackServerGroup", "releaseLock", "wait", "rollbackServerGroup"] - afterStages*.context.region == [ null , "us-west-2" , null , null , "us-east-1"] - } } diff --git a/orca-clouddriver/src/test/groovy/com/netflix/spinnaker/orca/clouddriver/pipeline/servergroup/CreateServerGroupStageSpec.groovy b/orca-clouddriver/src/test/groovy/com/netflix/spinnaker/orca/clouddriver/pipeline/servergroup/CreateServerGroupStageSpec.groovy index 89eb8fdc86..a330577591 100644 --- a/orca-clouddriver/src/test/groovy/com/netflix/spinnaker/orca/clouddriver/pipeline/servergroup/CreateServerGroupStageSpec.groovy +++ b/orca-clouddriver/src/test/groovy/com/netflix/spinnaker/orca/clouddriver/pipeline/servergroup/CreateServerGroupStageSpec.groovy @@ -20,7 +20,6 @@ import com.netflix.spinnaker.kork.dynamicconfig.SpringDynamicConfigService import com.netflix.spinnaker.orca.ExecutionStatus import com.netflix.spinnaker.orca.clouddriver.pipeline.servergroup.strategies.DeployStagePreProcessor import com.netflix.spinnaker.orca.clouddriver.utils.TrafficGuard -import com.netflix.spinnaker.orca.locks.LockingConfigurationProperties import com.netflix.spinnaker.orca.pipeline.StageDefinitionBuilder import com.netflix.spinnaker.orca.pipeline.model.Task import org.springframework.mock.env.MockEnvironment @@ -37,11 +36,10 @@ class CreateServerGroupStageSpec extends Specification { def deployStagePreProcessor = Mock(DeployStagePreProcessor) def trafficGuard = Stub(TrafficGuard) def env = new MockEnvironment() - def lockingConfig = new LockingConfigurationProperties(new SpringDynamicConfigService(environment: env)) @Subject def createServerGroupStage = new CreateServerGroupStage( - rollbackClusterStage: new RollbackClusterStage(trafficGuard, lockingConfig), + rollbackClusterStage: new RollbackClusterStage(), destroyServerGroupStage: new DestroyServerGroupStage(), deployStagePreProcessors: [ deployStagePreProcessor ] ) diff --git a/orca-clouddriver/src/test/groovy/com/netflix/spinnaker/orca/clouddriver/pipeline/servergroup/strategies/HighlanderStrategySpec.groovy b/orca-clouddriver/src/test/groovy/com/netflix/spinnaker/orca/clouddriver/pipeline/servergroup/strategies/HighlanderStrategySpec.groovy index e4efce706f..f84392db00 100644 --- a/orca-clouddriver/src/test/groovy/com/netflix/spinnaker/orca/clouddriver/pipeline/servergroup/strategies/HighlanderStrategySpec.groovy +++ b/orca-clouddriver/src/test/groovy/com/netflix/spinnaker/orca/clouddriver/pipeline/servergroup/strategies/HighlanderStrategySpec.groovy @@ -21,7 +21,6 @@ import com.netflix.spinnaker.kork.dynamicconfig.SpringDynamicConfigService import com.netflix.spinnaker.orca.clouddriver.pipeline.cluster.DisableClusterStage import com.netflix.spinnaker.orca.clouddriver.pipeline.cluster.ShrinkClusterStage import com.netflix.spinnaker.orca.clouddriver.utils.TrafficGuard -import com.netflix.spinnaker.orca.locks.LockingConfigurationProperties import com.netflix.spinnaker.orca.pipeline.model.Execution import com.netflix.spinnaker.orca.pipeline.model.Stage import com.netflix.spinnaker.orca.pipeline.model.SyntheticStageOwner @@ -33,12 +32,11 @@ class HighlanderStrategySpec extends Specification { def trafficGuard = Stub(TrafficGuard) def env = new MockEnvironment() - def lockingConfig = new LockingConfigurationProperties(new SpringDynamicConfigService(environment: env)) def dynamicConfigService = Mock(DynamicConfigService) - def disableClusterStage = new DisableClusterStage(trafficGuard, lockingConfig, dynamicConfigService) - def shrinkClusterStage = new ShrinkClusterStage(trafficGuard, lockingConfig, dynamicConfigService, disableClusterStage) + def disableClusterStage = new DisableClusterStage(dynamicConfigService) + def shrinkClusterStage = new ShrinkClusterStage(dynamicConfigService, disableClusterStage) @Unroll def "should compose flow"() { diff --git a/orca-clouddriver/src/test/groovy/com/netflix/spinnaker/orca/clouddriver/pipeline/servergroup/strategies/RedBlackStrategySpec.groovy b/orca-clouddriver/src/test/groovy/com/netflix/spinnaker/orca/clouddriver/pipeline/servergroup/strategies/RedBlackStrategySpec.groovy index 1b4f53ee5b..044bf92e69 100644 --- a/orca-clouddriver/src/test/groovy/com/netflix/spinnaker/orca/clouddriver/pipeline/servergroup/strategies/RedBlackStrategySpec.groovy +++ b/orca-clouddriver/src/test/groovy/com/netflix/spinnaker/orca/clouddriver/pipeline/servergroup/strategies/RedBlackStrategySpec.groovy @@ -23,7 +23,6 @@ import com.netflix.spinnaker.orca.clouddriver.pipeline.cluster.DisableClusterSta import com.netflix.spinnaker.orca.clouddriver.pipeline.cluster.ScaleDownClusterStage import com.netflix.spinnaker.orca.clouddriver.pipeline.cluster.ShrinkClusterStage import com.netflix.spinnaker.orca.clouddriver.utils.TrafficGuard -import com.netflix.spinnaker.orca.locks.LockingConfigurationProperties import com.netflix.spinnaker.orca.pipeline.WaitStage import com.netflix.spinnaker.orca.pipeline.model.Execution import com.netflix.spinnaker.orca.pipeline.model.Stage @@ -35,13 +34,12 @@ class RedBlackStrategySpec extends Specification { def trafficGuard = Stub(TrafficGuard) def env = new MockEnvironment() - def config = new LockingConfigurationProperties(new SpringDynamicConfigService(environment: env)) def dynamicConfigService = Mock(DynamicConfigService) - def disableClusterStage = new DisableClusterStage(trafficGuard, config, dynamicConfigService) - def shrinkClusterStage = new ShrinkClusterStage(trafficGuard, config, dynamicConfigService, disableClusterStage) - def scaleDownClusterStage = new ScaleDownClusterStage(trafficGuard, config, dynamicConfigService) + def disableClusterStage = new DisableClusterStage(dynamicConfigService) + def shrinkClusterStage = new ShrinkClusterStage(dynamicConfigService, disableClusterStage) + def scaleDownClusterStage = new ScaleDownClusterStage(dynamicConfigService) def waitStage = new WaitStage() def "should compose flow"() { diff --git a/orca-clouddriver/src/test/groovy/com/netflix/spinnaker/orca/clouddriver/pipeline/servergroup/strategies/RollingRedBlackStrategySpec.groovy b/orca-clouddriver/src/test/groovy/com/netflix/spinnaker/orca/clouddriver/pipeline/servergroup/strategies/RollingRedBlackStrategySpec.groovy index a01da6cc1a..b21d4ba757 100644 --- a/orca-clouddriver/src/test/groovy/com/netflix/spinnaker/orca/clouddriver/pipeline/servergroup/strategies/RollingRedBlackStrategySpec.groovy +++ b/orca-clouddriver/src/test/groovy/com/netflix/spinnaker/orca/clouddriver/pipeline/servergroup/strategies/RollingRedBlackStrategySpec.groovy @@ -11,7 +11,6 @@ import com.netflix.spinnaker.orca.clouddriver.pipeline.servergroup.support.Deter import com.netflix.spinnaker.orca.clouddriver.utils.TrafficGuard import com.netflix.spinnaker.orca.front50.pipeline.PipelineStage import com.netflix.spinnaker.orca.kato.pipeline.support.ResizeStrategy -import com.netflix.spinnaker.orca.locks.LockingConfigurationProperties import com.netflix.spinnaker.orca.pipeline.WaitStage import com.netflix.spinnaker.orca.pipeline.model.SyntheticStageOwner import org.springframework.mock.env.MockEnvironment @@ -22,11 +21,10 @@ import static com.netflix.spinnaker.orca.test.model.ExecutionBuilder.stage class RollingRedBlackStrategySpec extends Specification { def env = new MockEnvironment() - def config = new LockingConfigurationProperties(new SpringDynamicConfigService(environment: env)) def dynamicConfigService = Mock(DynamicConfigService) def trafficGuard = Mock(TrafficGuard) def disableServerGroupStage = new DisableServerGroupStage(dynamicConfigService) - def scaleDownClusterStage = new ScaleDownClusterStage(trafficGuard, config, dynamicConfigService) + def scaleDownClusterStage = new ScaleDownClusterStage(dynamicConfigService) def resizeServerGroupStage = new ResizeServerGroupStage() def waitStage = new WaitStage() def pipelineStage = Mock(PipelineStage) diff --git a/orca-clouddriver/src/test/groovy/com/netflix/spinnaker/orca/clouddriver/pipeline/servergroup/support/TargetServerGroupLinearStageSupportSpec.groovy b/orca-clouddriver/src/test/groovy/com/netflix/spinnaker/orca/clouddriver/pipeline/servergroup/support/TargetServerGroupLinearStageSupportSpec.groovy index 06b93152f5..6f48e2d680 100644 --- a/orca-clouddriver/src/test/groovy/com/netflix/spinnaker/orca/clouddriver/pipeline/servergroup/support/TargetServerGroupLinearStageSupportSpec.groovy +++ b/orca-clouddriver/src/test/groovy/com/netflix/spinnaker/orca/clouddriver/pipeline/servergroup/support/TargetServerGroupLinearStageSupportSpec.groovy @@ -18,7 +18,6 @@ package com.netflix.spinnaker.orca.clouddriver.pipeline.servergroup.support import com.netflix.spinnaker.kork.dynamicconfig.SpringDynamicConfigService import com.netflix.spinnaker.orca.clouddriver.utils.TrafficGuard -import com.netflix.spinnaker.orca.locks.LockingConfigurationProperties import com.netflix.spinnaker.orca.pipeline.graph.StageGraphBuilder import org.springframework.core.env.StandardEnvironment import spock.lang.Specification @@ -30,13 +29,11 @@ class TargetServerGroupLinearStageSupportSpec extends Specification { def resolver = Stub(TargetServerGroupResolver) def trafficGuard = Stub(TrafficGuard) - def lockingConfig = new LockingConfigurationProperties(new SpringDynamicConfigService(environment: new StandardEnvironment())) def supportStage = new TestSupport() def setup() { supportStage.resolver = resolver supportStage.trafficGuard = trafficGuard - supportStage.lockingConfigurationProperties = lockingConfig } @Unroll diff --git a/orca-clouddriver/src/test/java/com/netflix/spinnaker/orca/clouddriver/pipeline/providers/cf/CloudFoundryDeployStagePreProcessorTest.java b/orca-clouddriver/src/test/java/com/netflix/spinnaker/orca/clouddriver/pipeline/providers/cf/CloudFoundryDeployStagePreProcessorTest.java index e40a11ac52..03db7636b9 100644 --- a/orca-clouddriver/src/test/java/com/netflix/spinnaker/orca/clouddriver/pipeline/providers/cf/CloudFoundryDeployStagePreProcessorTest.java +++ b/orca-clouddriver/src/test/java/com/netflix/spinnaker/orca/clouddriver/pipeline/providers/cf/CloudFoundryDeployStagePreProcessorTest.java @@ -31,7 +31,7 @@ import org.junit.jupiter.api.Test; class CloudFoundryDeployStagePreProcessorTest { - private RollbackClusterStage rollbackClusterStage = new RollbackClusterStage(null, null); + private RollbackClusterStage rollbackClusterStage = new RollbackClusterStage(); private ServerGroupForceCacheRefreshStage serverGroupForceCacheRefreshStage = new ServerGroupForceCacheRefreshStage(); private CloudFoundryDeployStagePreProcessor preProcessor = diff --git a/orca-core/src/main/java/com/netflix/spinnaker/orca/locks/ExecutionCompleteLockReleasingListener.java b/orca-core/src/main/java/com/netflix/spinnaker/orca/locks/ExecutionCompleteLockReleasingListener.java deleted file mode 100644 index 67f6365d02..0000000000 --- a/orca-core/src/main/java/com/netflix/spinnaker/orca/locks/ExecutionCompleteLockReleasingListener.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright 2018 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.netflix.spinnaker.orca.locks; - -import static com.netflix.spinnaker.orca.notifications.AbstractPollingNotificationAgent.AGENT_MDC_KEY; - -import com.netflix.spinnaker.orca.events.ExecutionComplete; -import com.netflix.spinnaker.orca.pipeline.AcquireLockStage; -import com.netflix.spinnaker.orca.pipeline.model.Execution; -import com.netflix.spinnaker.orca.pipeline.persistence.ExecutionRepository; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.slf4j.MDC; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationListener; -import org.springframework.stereotype.Component; - -@Component -public class ExecutionCompleteLockReleasingListener - implements ApplicationListener { - private final Logger logger = LoggerFactory.getLogger(getClass()); - private final ExecutionRepository executionRepository; - private final LockManager lockManager; - private final LockingConfigurationProperties lockingConfigurationProperties; - - @Autowired - public ExecutionCompleteLockReleasingListener( - ExecutionRepository executionRepository, - LockManager lockManager, - LockingConfigurationProperties lockingConfigurationProperties) { - this.executionRepository = executionRepository; - this.lockManager = lockManager; - this.lockingConfigurationProperties = lockingConfigurationProperties; - } - - @Override - public void onApplicationEvent(ExecutionComplete event) { - if (!lockingConfigurationProperties.isEnabled()) { - return; - } - if (event.getStatus().isHalt()) { - try { - MDC.put(AGENT_MDC_KEY, this.getClass().getSimpleName()); - - Execution execution = - executionRepository.retrieve(event.getExecutionType(), event.getExecutionId()); - execution - .getStages() - .forEach( - s -> { - if (AcquireLockStage.PIPELINE_TYPE.equals(s.getType())) { - try { - LockContext lc = - s.mapTo("/lock", LockContext.LockContextBuilder.class) - .withStage(s) - .build(); - lockManager.releaseLock( - lc.getLockName(), lc.getLockValue(), lc.getLockHolder()); - } catch (LockFailureException lfe) { - logger.info( - "Failure releasing lock in ExecutionCompleteLockReleasingListener - ignoring", - lfe); - } - } - }); - } finally { - MDC.remove(AGENT_MDC_KEY); - } - } - } -} diff --git a/orca-core/src/main/java/com/netflix/spinnaker/orca/locks/LockContext.java b/orca-core/src/main/java/com/netflix/spinnaker/orca/locks/LockContext.java deleted file mode 100644 index f5ae899598..0000000000 --- a/orca-core/src/main/java/com/netflix/spinnaker/orca/locks/LockContext.java +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Copyright 2018 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.netflix.spinnaker.orca.locks; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.netflix.spinnaker.orca.pipeline.model.Execution; -import com.netflix.spinnaker.orca.pipeline.model.PipelineTrigger; -import com.netflix.spinnaker.orca.pipeline.model.Stage; -import java.util.Objects; -import java.util.Optional; - -public class LockContext { - public static class LockContextBuilder { - - public static class LockValueBuilder { - private String application; - private String type; - private String id; - private Stage stage; - - LockValueBuilder() { - this(null, null, null, null); - } - - @JsonCreator - public LockValueBuilder( - @JsonProperty("application") String application, - @JsonProperty("type") String type, - @JsonProperty("id") String id) { - this(application, type, id, null); - } - - LockValueBuilder(String application, String type, String id, Stage stage) { - this.application = application; - this.type = type; - this.id = id; - this.stage = stage; - } - - public LockValueBuilder withStage(Stage stage) { - this.stage = stage; - return this; - } - - public LockManager.LockValue build() { - final Optional execution = Optional.ofNullable(stage).map(Stage::getExecution); - - final String application = - Optional.ofNullable(this.application) - .orElseGet(() -> execution.map(Execution::getApplication).orElse(null)); - - final String type = - Optional.ofNullable(this.type) - .orElseGet( - () -> - execution - .map(Execution::getType) - .map(Execution.ExecutionType::toString) - .orElse(null)); - - final String id = - Optional.ofNullable(this.id) - .orElseGet( - () -> { - if (!execution.isPresent()) { - return null; - } - - Optional next = execution; - Execution rootExecution; - do { - rootExecution = next.get(); - next = - next.filter(e -> e.getTrigger() instanceof PipelineTrigger) - .map(e -> ((PipelineTrigger) e.getTrigger()).getParentStage()) - .map(Stage::getExecution); - } while (next.isPresent()); - - return rootExecution.getId(); - }); - - return new LockManager.LockValue(application, type, id); - } - } - - private String lockName; - private LockValueBuilder lockValue; - private String lockHolder; - private Stage stage; - - public LockContextBuilder() { - this(null, null, null, null); - } - - @JsonCreator - public LockContextBuilder( - @JsonProperty("lockName") String lockName, - @JsonProperty("lockValue") LockValueBuilder lockValue, - @JsonProperty("lockHolder") String lockHolder) { - this(lockName, lockValue, lockHolder, null); - } - - LockContextBuilder( - String lockName, LockValueBuilder lockValue, String lockHolder, Stage stage) { - this.lockName = lockName; - this.lockValue = lockValue; - this.lockHolder = lockHolder; - this.stage = stage; - } - - public LockContextBuilder withStage(Stage stage) { - this.stage = stage; - Optional.ofNullable(lockValue).ifPresent(lv -> lv.withStage(stage)); - return this; - } - - public LockContext build() { - final LockManager.LockValue lockValue = - Optional.ofNullable(this.lockValue) - .orElseGet(() -> new LockValueBuilder().withStage(stage)) - .build(); - - final String lockHolder = - Optional.ofNullable(this.lockHolder) - .orElseGet(() -> Optional.ofNullable(stage).map(Stage::getId).orElse(null)); - - return new LockContext(lockName, lockValue, lockHolder); - } - } - - private final String lockName; - private final LockManager.LockValue lockValue; - private final String lockHolder; - - public LockContext(String lockName, LockManager.LockValue lockValue, String lockHolder) { - this.lockName = Objects.requireNonNull(lockName); - this.lockValue = Objects.requireNonNull(lockValue); - this.lockHolder = Objects.requireNonNull(lockHolder); - } - - public String getLockName() { - return lockName; - } - - public LockManager.LockValue getLockValue() { - return lockValue; - } - - public String getLockHolder() { - return lockHolder; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - LockContext that = (LockContext) o; - return Objects.equals(lockName, that.lockName) - && Objects.equals(lockValue, that.lockValue) - && Objects.equals(lockHolder, that.lockHolder); - } - - @Override - public int hashCode() { - return Objects.hash(lockName, lockValue, lockHolder); - } -} diff --git a/orca-core/src/main/java/com/netflix/spinnaker/orca/locks/LockExtendingTaskExecutionInterceptor.java b/orca-core/src/main/java/com/netflix/spinnaker/orca/locks/LockExtendingTaskExecutionInterceptor.java deleted file mode 100644 index 7f4b1d3926..0000000000 --- a/orca-core/src/main/java/com/netflix/spinnaker/orca/locks/LockExtendingTaskExecutionInterceptor.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright 2018 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.netflix.spinnaker.orca.locks; - -import com.netflix.spinnaker.orca.ExecutionStatus; -import com.netflix.spinnaker.orca.Task; -import com.netflix.spinnaker.orca.TaskExecutionInterceptor; -import com.netflix.spinnaker.orca.TaskResult; -import com.netflix.spinnaker.orca.pipeline.AcquireLockStage; -import com.netflix.spinnaker.orca.pipeline.ReleaseLockStage; -import com.netflix.spinnaker.orca.pipeline.model.Stage; -import com.netflix.spinnaker.orca.pipeline.tasks.AcquireLockTask; -import com.netflix.spinnaker.orca.pipeline.tasks.ReleaseLockTask; -import java.time.Duration; -import java.util.*; -import java.util.concurrent.atomic.AtomicInteger; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -@Component -public class LockExtendingTaskExecutionInterceptor implements TaskExecutionInterceptor { - private final Logger log = LoggerFactory.getLogger(getClass()); - private final LockManager lockManager; - private final LockingConfigurationProperties lockingConfigurationProperties; - - @Autowired - public LockExtendingTaskExecutionInterceptor( - LockManager lockManager, LockingConfigurationProperties lockingConfigurationProperties) { - this.lockManager = lockManager; - this.lockingConfigurationProperties = lockingConfigurationProperties; - } - - @Override - public long maxTaskBackoff() { - return Duration.ofSeconds( - lockingConfigurationProperties.getTtlSeconds() - - lockingConfigurationProperties.getBackoffBufferSeconds()) - .toMillis(); - } - - @Override - public Stage beforeTaskExecution(Task task, Stage stage) { - extendLocks(task, stage); - return stage; - } - - @Override - public TaskResult afterTaskExecution(Task task, Stage stage, TaskResult taskResult) { - extendLocks(task, stage); - return taskResult; - } - - private void extendLocks(Task task, Stage stage) { - if (!lockingConfigurationProperties.isEnabled()) { - return; - } - if (task instanceof AcquireLockTask || task instanceof ReleaseLockTask) { - return; - } - - Map heldLocks = new HashMap<>(); - Set lockTypes = - new HashSet<>( - Arrays.asList(AcquireLockStage.PIPELINE_TYPE, ReleaseLockStage.PIPELINE_TYPE)); - stage.getExecution().getStages().stream() - .filter(s -> lockTypes.contains(s.getType()) && s.getStatus() == ExecutionStatus.SUCCEEDED) - .forEach( - s -> { - LockContext lc = - s.mapTo("/lock", LockContext.LockContextBuilder.class).withStage(s).build(); - AtomicInteger count = - heldLocks.computeIfAbsent( - new HeldLock(lc.getLockName(), lc.getLockValue()), - hl -> new AtomicInteger(0)); - if (AcquireLockStage.PIPELINE_TYPE.equals(s.getType())) { - count.incrementAndGet(); - } else { - count.decrementAndGet(); - } - }); - - heldLocks.entrySet().stream() - .filter(me -> me.getValue().get() > 0) - .map(Map.Entry::getKey) - .forEach( - hl -> { - log.debug("extending lock {} held by {}", hl.lockName, hl.lockValue); - lockManager.extendLock( - hl.lockName, hl.lockValue, lockingConfigurationProperties.getTtlSeconds()); - }); - } - - private static class HeldLock { - final String lockName; - final LockManager.LockValue lockValue; - - HeldLock(String lockName, LockManager.LockValue lockValue) { - this.lockName = lockName; - this.lockValue = lockValue; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - HeldLock heldLock = (HeldLock) o; - return Objects.equals(lockName, heldLock.lockName) - && Objects.equals(lockValue, heldLock.lockValue); - } - - @Override - public int hashCode() { - return Objects.hash(lockName, lockValue); - } - } -} diff --git a/orca-core/src/main/java/com/netflix/spinnaker/orca/locks/LockFailureException.java b/orca-core/src/main/java/com/netflix/spinnaker/orca/locks/LockFailureException.java deleted file mode 100644 index 0a5daa893f..0000000000 --- a/orca-core/src/main/java/com/netflix/spinnaker/orca/locks/LockFailureException.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2018 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.netflix.spinnaker.orca.locks; - -import java.util.Optional; -import javax.annotation.Nullable; - -public class LockFailureException extends RuntimeException { - private final String lockName; - private final LockManager.LockValue currentLockValue; - - private static String buildMessage( - String lockName, @Nullable LockManager.LockValue currentLockValue) { - Optional lv = Optional.ofNullable(currentLockValue); - return "Failed to acquire lock " - + lockName - + " currently held by " - + lv.map(LockManager.LockValue::getApplication).orElse("UNKNOWN") - + "/" - + lv.map(LockManager.LockValue::getType).orElse("UNKNOWN") - + "/" - + lv.map(LockManager.LockValue::getId).orElse("UNKNOWN"); - } - - public LockFailureException(String lockName, @Nullable LockManager.LockValue currentLockValue) { - super(buildMessage(lockName, currentLockValue)); - this.lockName = lockName; - this.currentLockValue = currentLockValue; - } - - public String getLockName() { - return lockName; - } - - @Nullable - public LockManager.LockValue getCurrentLockValue() { - return currentLockValue; - } -} diff --git a/orca-core/src/main/java/com/netflix/spinnaker/orca/locks/LockManager.java b/orca-core/src/main/java/com/netflix/spinnaker/orca/locks/LockManager.java deleted file mode 100644 index c8d19347b7..0000000000 --- a/orca-core/src/main/java/com/netflix/spinnaker/orca/locks/LockManager.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright 2018 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.netflix.spinnaker.orca.locks; - -import java.util.Objects; - -/** - * Manages acquisition / release of locks. - * - *

The lock is held with a value which is the combination of application, type, and id. This - * would typically be the execution application, type (pipeline or orchestration) and execution id. - * - *

A named lock can be acquired if either it is not currently locked, or if the current value of - * the lock matches the supplied lockValue fields. - * - *

A lock has a TTL to ensure that in the event of an unexpected failure or JVM exit that - * eventually locks are released - the general expectation is that a call to acquireLock should be - * accompanied by a call to releaseLock. - * - *

Multiple holders can acquire the same lock by supplying the same lockValue fields, and a lock - * continues to be held until all holders have issued a releaseLock. - * - *

A stage that operates on a particular cluster should acquire the lock for that cluster. Any - * synthetic stages can also acquire the same lock. For example: - * - *

As an example, a deploy with Red/Black would go through the following: - * - *

lockValue = new LockValue(application, execution.type, execution.id) createServerGroup - * acquireLock(clusterName, lockValue, createServerGroup.stage.id) deploy waitForUpInstances - * - *

disableCluster acquireLock(clusterName, lockValue, disableCluster.stage.id) disableCluster - * waitForDisableCluster releaseLock(clusterName, lockValue, disableCluster.stage.id) - * - *

scaleDownCluster acquireLock(clusterName, lockValue, scaleDownCluster.stage.id) - * scaleDownCluster waitForScaleDown releaseLock(clusterName, lockValue, scaleDownCluster.stage.id) - * - *

releaseLock(clusterName, lockValue, createServerGroup.stage.id) - * - *

The lifespan of the lock would be the entire duration of the createServerGroup stage including - * all the child stages (disableCluster, scaleDownCluster). - */ -public interface LockManager { - - class LockValue { - final String application; - final String type; - final String id; - - public LockValue(String application, String type, String id) { - this.application = Objects.requireNonNull(application); - this.type = Objects.requireNonNull(type); - this.id = Objects.requireNonNull(id); - } - - public String getApplication() { - return application; - } - - public String getType() { - return type; - } - - public String getId() { - return id; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - LockValue lockValue = (LockValue) o; - return Objects.equals(application, lockValue.application) - && Objects.equals(type, lockValue.type) - && Objects.equals(id, lockValue.id); - } - - @Override - public int hashCode() { - return Objects.hash(application, type, id); - } - - @Override - public String toString() { - return "LockValue{" - + "application='" - + application - + '\'' - + ", type='" - + type - + '\'' - + ", id='" - + id - + '\'' - + '}'; - } - } - - /** - * Acquires a named lock. - * - * @param lockName The name of the lock. - * @param lockValue The value of the lock - if the lock is already held with this value, - * acquisition is successful otherwise lock acquisition fails. - * @param lockHolder The holder of the lock. - * @param ttlSeconds How long to acquire for or extend an existing lock for. - * @throws LockFailureException if the lock is currently held with a different - * lockValueApplication/lockValueType/lockValue - */ - void acquireLock(String lockName, LockValue lockValue, String lockHolder, int ttlSeconds) - throws LockFailureException; - - /** - * Extends a named lock ttl. - * - * @param lockName The name of the lock. - * @param lockValue The value of the lock - if the lock is already held with this value, the ttl - * extension is successful otherwise lock extension fails. - * @param ttlSeconds How long to extend the lock for. - * @throws LockFailureException if the lock is currently held with a different - * lockValueApplication/lockValueType/lockValue - */ - void extendLock(String lockName, LockValue lockValue, int ttlSeconds) throws LockFailureException; - - /** - * Releases a named lock for a specific lockHolder. - * - *

After release, if there are no additional lock holders the lock itself is freed. - * - * @param lockName The name of the lock. - * @param lockValue The value of the lock - An existing lock must be held with this value for - * release to succeed. - * @param lockHolder The holder of the lock. - */ - void releaseLock(String lockName, LockValue lockValue, String lockHolder); -} diff --git a/orca-core/src/main/java/com/netflix/spinnaker/orca/locks/LockingConfigurationProperties.java b/orca-core/src/main/java/com/netflix/spinnaker/orca/locks/LockingConfigurationProperties.java deleted file mode 100644 index a9ec0d841b..0000000000 --- a/orca-core/src/main/java/com/netflix/spinnaker/orca/locks/LockingConfigurationProperties.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2018 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.netflix.spinnaker.orca.locks; - -import com.netflix.spinnaker.kork.dynamicconfig.DynamicConfigService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.stereotype.Component; - -@Component -@ConfigurationProperties("locking") -public class LockingConfigurationProperties { - private boolean learningMode = true; - private boolean enabled = false; - private int ttlSeconds = 120; - private int backoffBufferSeconds = 10; - private final DynamicConfigService dynamicConfigService; - - @Autowired - public LockingConfigurationProperties(DynamicConfigService dynamicConfigService) { - this.dynamicConfigService = dynamicConfigService; - } - - public boolean isLearningMode() { - return dynamicConfigService.getConfig(Boolean.class, "locking.learning-mode", learningMode); - } - - public void setLearningMode(boolean learningMode) { - this.learningMode = learningMode; - } - - public boolean isEnabled() { - return dynamicConfigService.isEnabled("locking", enabled); - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - - public int getTtlSeconds() { - return dynamicConfigService.getConfig(Integer.class, "locking.ttl-seconds", ttlSeconds); - } - - public void setTtlSeconds(int ttlSeconds) { - this.ttlSeconds = ttlSeconds; - } - - public int getBackoffBufferSeconds() { - return dynamicConfigService.getConfig( - Integer.class, "locking.backoff-buffer-seconds", backoffBufferSeconds); - } - - public void setBackoffBufferSeconds(int backoffBufferSeconds) { - this.backoffBufferSeconds = backoffBufferSeconds; - } -} diff --git a/orca-core/src/main/java/com/netflix/spinnaker/orca/pipeline/AcquireLockStage.java b/orca-core/src/main/java/com/netflix/spinnaker/orca/pipeline/AcquireLockStage.java deleted file mode 100644 index 97ada50da3..0000000000 --- a/orca-core/src/main/java/com/netflix/spinnaker/orca/pipeline/AcquireLockStage.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2018 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.netflix.spinnaker.orca.pipeline; - -import com.netflix.spinnaker.orca.pipeline.model.Stage; -import com.netflix.spinnaker.orca.pipeline.tasks.AcquireLockTask; -import javax.annotation.Nonnull; -import org.springframework.stereotype.Component; - -@Component -public class AcquireLockStage implements StageDefinitionBuilder { - - public static final String PIPELINE_TYPE = "acquireLock"; - - @Nonnull - @Override - public String getType() { - return PIPELINE_TYPE; - } - - @Override - public void taskGraph(@Nonnull Stage stage, @Nonnull TaskNode.Builder builder) { - builder.withTask("acquireLock", AcquireLockTask.class); - } - - @Override - public void prepareStageForRestart(@Nonnull Stage stage) { - stage.getContext().remove("completeOtherBranchesThenFail"); - } -} diff --git a/orca-core/src/main/java/com/netflix/spinnaker/orca/pipeline/ReleaseLockStage.java b/orca-core/src/main/java/com/netflix/spinnaker/orca/pipeline/ReleaseLockStage.java deleted file mode 100644 index 6b6c89663c..0000000000 --- a/orca-core/src/main/java/com/netflix/spinnaker/orca/pipeline/ReleaseLockStage.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2018 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.netflix.spinnaker.orca.pipeline; - -import com.netflix.spinnaker.orca.pipeline.model.Stage; -import com.netflix.spinnaker.orca.pipeline.tasks.DetermineLockTask; -import com.netflix.spinnaker.orca.pipeline.tasks.ReleaseLockTask; -import javax.annotation.Nonnull; -import org.springframework.stereotype.Component; - -@Component -public class ReleaseLockStage implements StageDefinitionBuilder { - public static final String PIPELINE_TYPE = "releaseLock"; - - @Nonnull - @Override - public String getType() { - return PIPELINE_TYPE; - } - - @Override - public void taskGraph(@Nonnull Stage stage, @Nonnull TaskNode.Builder builder) { - builder - .withTask("determineLock", DetermineLockTask.class) - .withTask("releaseLock", ReleaseLockTask.class); - } -} diff --git a/orca-core/src/main/java/com/netflix/spinnaker/orca/pipeline/tasks/AcquireLockTask.java b/orca-core/src/main/java/com/netflix/spinnaker/orca/pipeline/tasks/AcquireLockTask.java deleted file mode 100644 index 9b212d175e..0000000000 --- a/orca-core/src/main/java/com/netflix/spinnaker/orca/pipeline/tasks/AcquireLockTask.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright 2018 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License") - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.netflix.spinnaker.orca.pipeline.tasks; - -import com.netflix.spinnaker.orca.ExecutionStatus; -import com.netflix.spinnaker.orca.Task; -import com.netflix.spinnaker.orca.TaskResult; -import com.netflix.spinnaker.orca.exceptions.DefaultExceptionHandler; -import com.netflix.spinnaker.orca.exceptions.ExceptionHandler; -import com.netflix.spinnaker.orca.locks.LockContext; -import com.netflix.spinnaker.orca.locks.LockFailureException; -import com.netflix.spinnaker.orca.locks.LockManager; -import com.netflix.spinnaker.orca.locks.LockingConfigurationProperties; -import com.netflix.spinnaker.orca.pipeline.model.Stage; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import javax.annotation.Nonnull; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -@Component -public class AcquireLockTask implements Task { - - private final LockManager lockManager; - private final LockingConfigurationProperties lockingConfigurationProperties; - - @Autowired - public AcquireLockTask( - LockManager lockManager, LockingConfigurationProperties lockingConfigurationProperties) { - this.lockManager = lockManager; - this.lockingConfigurationProperties = lockingConfigurationProperties; - } - - @Nonnull - @Override - public TaskResult execute(@Nonnull Stage stage) { - LockContext lock = - stage.mapTo("/lock", LockContext.LockContextBuilder.class).withStage(stage).build(); - try { - lockManager.acquireLock( - lock.getLockName(), - lock.getLockValue(), - lock.getLockHolder(), - lockingConfigurationProperties.getTtlSeconds()); - return TaskResult.builder(ExecutionStatus.SUCCEEDED) - .context(Collections.singletonMap("lock", lock)) - .build(); - } catch (LockFailureException lfe) { - Map resultContext = new HashMap<>(); - ExceptionHandler.Response exResult = new DefaultExceptionHandler().handle("acquireLock", lfe); - exResult.getDetails().put("lockName", lfe.getLockName()); - exResult.getDetails().put("currentLockValue", lfe.getCurrentLockValue()); - resultContext.put("exception", exResult); - - // Changes lock acquisition failure to stop the current branch, and fail - // the pipeline when other branches complete. If one operation in parallel - // is unable to acquire a lock, this would mean other operations would - // complete normally (if they are able to lock) before the overall execution - // fails. - // - // This is preferable to one operation failing to acquire a lock leaving other - // operations in a partially completed state. Additionally this makes the stage - // restart story a bit more sensible - we can restart the failed Acquire Lock - // stage and the pipeline will proceed, and we haven't failed a bunch of other - // stages halfway through so the pipeline will proceed for any downstream join - // points. - resultContext.put("completeOtherBranchesThenFail", true); - return TaskResult.builder(ExecutionStatus.STOPPED).context(resultContext).build(); - } - } -} diff --git a/orca-core/src/main/java/com/netflix/spinnaker/orca/pipeline/tasks/DetermineLockTask.java b/orca-core/src/main/java/com/netflix/spinnaker/orca/pipeline/tasks/DetermineLockTask.java deleted file mode 100644 index d81b1ef669..0000000000 --- a/orca-core/src/main/java/com/netflix/spinnaker/orca/pipeline/tasks/DetermineLockTask.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright 2018 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License") - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.netflix.spinnaker.orca.pipeline.tasks; - -import com.netflix.spinnaker.orca.ExecutionStatus; -import com.netflix.spinnaker.orca.Task; -import com.netflix.spinnaker.orca.TaskResult; -import com.netflix.spinnaker.orca.locks.LockContext; -import com.netflix.spinnaker.orca.locks.LockingConfigurationProperties; -import com.netflix.spinnaker.orca.pipeline.AcquireLockStage; -import com.netflix.spinnaker.orca.pipeline.model.Stage; -import com.netflix.spinnaker.orca.pipeline.util.StageNavigator; -import java.util.Collections; -import java.util.Optional; -import javax.annotation.Nonnull; -import net.logstash.logback.argument.StructuredArguments; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -@Component -public class DetermineLockTask implements Task { - - private final StageNavigator stageNavigator; - private final LockingConfigurationProperties lockingConfigurationProperties; - private final Logger log = LoggerFactory.getLogger(getClass()); - - @Autowired - public DetermineLockTask( - StageNavigator stageNavigator, - LockingConfigurationProperties lockingConfigurationProperties) { - this.stageNavigator = stageNavigator; - this.lockingConfigurationProperties = lockingConfigurationProperties; - } - - @Nonnull - @Override - public TaskResult execute(@Nonnull Stage stage) { - Optional lockStageResult = - stageNavigator.ancestors(stage).stream() - .filter(r -> r.getStageBuilder() instanceof AcquireLockStage) - .filter( - r -> - stage.getParentStageId() == null - ? r.getStage().getParentStageId() == null - : stage.getParentStageId().equals(r.getStage().getParentStageId())) - .findFirst(); - - try { - final LockContext lockContext; - if (lockStageResult.isPresent()) { - final Stage lockStage = lockStageResult.get().getStage(); - lockContext = - lockStage - .mapTo("/lock", LockContext.LockContextBuilder.class) - .withStage(lockStage) - .build(); - } else { - lockContext = stage.mapTo("/lock", LockContext.LockContextBuilder.class).build(); - } - - return TaskResult.builder(ExecutionStatus.SUCCEEDED) - .context(Collections.singletonMap("lock", lockContext)) - .build(); - } catch (Exception ex) { - final boolean lockingEnabled = lockingConfigurationProperties.isEnabled(); - final boolean learningMode = lockingConfigurationProperties.isLearningMode(); - if (!lockingEnabled || learningMode) { - log.debug( - "DetermineLockTask failed. Ignoring due to {} {}", - StructuredArguments.kv("locking.enabled", lockingEnabled), - StructuredArguments.kv("locking.learningMode", learningMode), - ex); - LockContext lc = - new LockContext.LockContextBuilder("unknown", null, "unknown").withStage(stage).build(); - return TaskResult.builder(ExecutionStatus.SUCCEEDED) - .context(Collections.singletonMap("lock", lc)) - .build(); - } - throw new IllegalStateException( - "Unable to determine lock from context or previous lock stage", ex); - } - } -} diff --git a/orca-core/src/main/java/com/netflix/spinnaker/orca/pipeline/tasks/ReleaseLockTask.java b/orca-core/src/main/java/com/netflix/spinnaker/orca/pipeline/tasks/ReleaseLockTask.java deleted file mode 100644 index 6dc3ec74d8..0000000000 --- a/orca-core/src/main/java/com/netflix/spinnaker/orca/pipeline/tasks/ReleaseLockTask.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2018 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License") - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.netflix.spinnaker.orca.pipeline.tasks; - -import com.netflix.spinnaker.orca.Task; -import com.netflix.spinnaker.orca.TaskResult; -import com.netflix.spinnaker.orca.locks.LockContext; -import com.netflix.spinnaker.orca.locks.LockManager; -import com.netflix.spinnaker.orca.pipeline.model.Stage; -import javax.annotation.Nonnull; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -@Component -public class ReleaseLockTask implements Task { - - private final LockManager lockManager; - - @Autowired - public ReleaseLockTask(LockManager lockManager) { - this.lockManager = lockManager; - } - - @Nonnull - @Override - public TaskResult execute(@Nonnull Stage stage) { - final LockContext lock = - stage.mapTo("/lock", LockContext.LockContextBuilder.class).withStage(stage).build(); - lockManager.releaseLock(lock.getLockName(), lock.getLockValue(), lock.getLockHolder()); - return TaskResult.SUCCEEDED; - } -} diff --git a/orca-core/src/test/groovy/com/netflix/spinnaker/orca/locks/LockContextSpec.groovy b/orca-core/src/test/groovy/com/netflix/spinnaker/orca/locks/LockContextSpec.groovy deleted file mode 100644 index 802b2c5910..0000000000 --- a/orca-core/src/test/groovy/com/netflix/spinnaker/orca/locks/LockContextSpec.groovy +++ /dev/null @@ -1,122 +0,0 @@ -package com.netflix.spinnaker.orca.locks - -import com.netflix.spinnaker.orca.pipeline.model.Execution -import com.netflix.spinnaker.orca.pipeline.model.PipelineTrigger -import com.netflix.spinnaker.orca.pipeline.model.Stage -import com.netflix.spinnaker.orca.test.model.ExecutionBuilder -import spock.lang.Specification - -class LockContextSpec extends Specification { - - def "builder uses explicitly provided id in when present"() { - given: - def builder = new LockContext.LockContextBuilder.LockValueBuilder(application, lockType, explicitId, stage) - - expect: - builder.build() == expected - - where: - application = 'app' - lockType = 'pipeline' - explicitId = 'bacon' - - stage = ExecutionBuilder.stage { - context = [:] - } - - expectedId = explicitId - expected = new LockManager.LockValue(application, lockType, expectedId) - } - - def "builder uses execution id in simple execution"() { - given: - def builder = new LockContext.LockContextBuilder.LockValueBuilder(application, lockType, explicitId, stage) - - expect: - builder.build() == expected - - where: - application = 'app' - lockType = 'pipeline' - explicitId = null - - stage = ExecutionBuilder.stage { - context = [:] - } - - expectedId = stage.execution.id - expected = new LockManager.LockValue(application, lockType, expectedId) - } - - def "builder traverses up the hierarchy when execution is triggered by a PipelineTrigger"() { - given: - def builder = new LockContext.LockContextBuilder.LockValueBuilder(application, lockType, explicitId, stage) - - expect: - builder.build() == expected - - where: - application = 'app' - lockType = 'pipeline' - explicitId = null - - parentStage = ExecutionBuilder.stage { - type = 'pipeline' - } - - - exec = ExecutionBuilder.pipeline { - trigger = makeTrigger(parentStage) - ExecutionBuilder.stage { - type = 'acquireLock' - } - } - - stage = exec.stages[0] - - expectedId = parentStage.execution.id - expected = new LockManager.LockValue(application, lockType, expectedId) - - } - - def "builder traverses up the hierarchy multiple levels when execution is triggered by a PipelineTrigger"() { - given: - def builder = new LockContext.LockContextBuilder.LockValueBuilder(application, lockType, explicitId, stage) - - expect: - builder.build() == expected - - where: - application = 'app' - lockType = 'pipeline' - explicitId = null - - - - grandParentStage = ExecutionBuilder.stage { - type = 'pipeline' - } - - parentExec = childExec(grandParentStage) - - exec = childExec(parentExec.stages[0], 'acquireLock') - stage = exec.stages[0] - - expectedId = grandParentStage.execution.id - expected = new LockManager.LockValue(application, lockType, expectedId) - - } - - private PipelineTrigger makeTrigger(Stage parentStage) { - return new PipelineTrigger('pipeline', null, '[anonymous', [:], [], [], false, false, false, parentStage.execution, parentStage.id) - } - - private Execution childExec(Stage parentStage, String stageType = 'pipeline') { - return ExecutionBuilder.pipeline { - trigger = makeTrigger(parentStage) - ExecutionBuilder.stage { - type = stageType - } - } - } -} diff --git a/orca-core/src/test/groovy/com/netflix/spinnaker/orca/pipeline/tasks/AcquireLockTaskSpec.groovy b/orca-core/src/test/groovy/com/netflix/spinnaker/orca/pipeline/tasks/AcquireLockTaskSpec.groovy deleted file mode 100644 index 7c73b5fee6..0000000000 --- a/orca-core/src/test/groovy/com/netflix/spinnaker/orca/pipeline/tasks/AcquireLockTaskSpec.groovy +++ /dev/null @@ -1,124 +0,0 @@ -package com.netflix.spinnaker.orca.pipeline.tasks - -import com.netflix.spinnaker.kork.dynamicconfig.DynamicConfigService -import com.netflix.spinnaker.orca.ExecutionStatus -import com.netflix.spinnaker.orca.locks.LockContext -import com.netflix.spinnaker.orca.locks.LockFailureException -import com.netflix.spinnaker.orca.locks.LockManager -import com.netflix.spinnaker.orca.locks.LockingConfigurationProperties -import com.netflix.spinnaker.orca.pipeline.AcquireLockStage -import com.netflix.spinnaker.orca.pipeline.model.Execution -import com.netflix.spinnaker.orca.pipeline.model.Stage -import spock.lang.Specification -import spock.lang.Subject -import spock.lang.Unroll - -class AcquireLockTaskSpec extends Specification { - - LockManager lockManager = Mock(LockManager) - - DynamicConfigService dynamicConfigService = Stub(DynamicConfigService) { - getConfig(_ as Class, _ as String, _ as Object) >> { type, name, defaultValue -> return defaultValue } - isEnabled(_ as String, _ as Boolean) >> { flag, defaultValue -> return defaultValue } - } - - LockingConfigurationProperties props = new LockingConfigurationProperties(dynamicConfigService) - - @Subject - AcquireLockTask task = new AcquireLockTask(lockManager, props) - - def "should build default lock from stage"() { - given: - def ex = new Execution(Execution.ExecutionType.PIPELINE, application) - def stage = new Stage(ex, AcquireLockStage.PIPELINE_TYPE, [lock: [lockName: lockName]]) - def lc = new LockContext(lockName, lv(ex), stage.id) - - when: - def result = task.execute(stage) - - then: - 1 * lockManager.acquireLock(lc.lockName, lc.lockValue, lc.lockHolder, props.ttlSeconds) - result.status == ExecutionStatus.SUCCEEDED - result.context.lock == lc - - where: - application = 'fooapp' - lockName = 'testlock' - } - - def "a lock failure should STOP the stage and allow other branches to complete"() { - given: - def ex = new Execution(Execution.ExecutionType.PIPELINE, application) - def stage = new Stage(ex, AcquireLockStage.PIPELINE_TYPE, [lock: [lockName: lockName]]) - def lc = new LockContext(lockName, lv(ex), stage.id) - - when: - def result = task.execute(stage) - - then: - 1 * lockManager.acquireLock(lc.lockName, lc.lockValue, lc.lockHolder, props.ttlSeconds) >> { - throw new LockFailureException(lockName, currentLockValue) - } - - result.status == ExecutionStatus.STOPPED - result.context.exception.details.lockName == lockName - result.context.exception.details.currentLockValue == currentLockValue - result.context.completeOtherBranchesThenFail == true - - where: - currentApplication = 'barapp' - currentLockValue = lv(new Execution(Execution.ExecutionType.PIPELINE, currentApplication)) - application = 'fooapp' - lockName = 'testlock' - - } - - @Unroll - def "should prefer explicit context values for lock context when #desc"() { - given: - def stage = new Stage(execution, AcquireLockStage.PIPELINE_TYPE, [lock: [lockName: lockName, lockValue: contextLockValue, lockHolder: lockHolder]]) - def lc = new LockContext(lockName, defaultLockValue, stage.id) - - when: - def result = task.execute(stage) - - then: - 1 * lockManager.acquireLock(lc.lockName, expectedLockValue, lockHolder ? lockHolder : lc.lockHolder, props.ttlSeconds) - result.status == ExecutionStatus.SUCCEEDED - - where: - application = 'fooapp' - lockName = 'testlock' - - lvApp | lvId | lockHolder || desc - null | null | null || 'no explicit values provided' - 'barapp' | null | null || 'explicit lockValue application provided' - null | null | 'someholder' || 'explicit lockHolder provided' - 'bazapp' | 'bazid' | 'someholder' || 'both lockValue and lockHolder provided' - - contextLockValue = ctxLv(lvApp, lvId) - execution = new Execution(Execution.ExecutionType.PIPELINE, application) - defaultLockValue = lv(execution) - - expectedLockValue = contextLockValue == null ? defaultLockValue : - new LockManager.LockValue( - contextLockValue.application ?: defaultLockValue.application, - contextLockValue.type ?: defaultLockValue.type, - contextLockValue.id ?: defaultLockValue.id) - } - - private static Map ctxLv(String application, String id) { - if (!(application || id)) { - return null - } - return [ - application: application, - type : 'pipeline', - id : id - ] - } - - private static LockManager.LockValue lv(Execution ex) { - return new LockManager.LockValue(ex.application, ex.type.toString(), ex.id) - } -} diff --git a/orca-core/src/test/groovy/com/netflix/spinnaker/orca/pipeline/tasks/DetermineLockTaskSpec.groovy b/orca-core/src/test/groovy/com/netflix/spinnaker/orca/pipeline/tasks/DetermineLockTaskSpec.groovy deleted file mode 100644 index 1b80a714dc..0000000000 --- a/orca-core/src/test/groovy/com/netflix/spinnaker/orca/pipeline/tasks/DetermineLockTaskSpec.groovy +++ /dev/null @@ -1,147 +0,0 @@ -package com.netflix.spinnaker.orca.pipeline.tasks - -import com.netflix.spinnaker.kork.dynamicconfig.SpringDynamicConfigService -import com.netflix.spinnaker.orca.ExecutionStatus -import com.netflix.spinnaker.orca.locks.LockingConfigurationProperties -import com.netflix.spinnaker.orca.pipeline.AcquireLockStage -import com.netflix.spinnaker.orca.pipeline.ReleaseLockStage -import com.netflix.spinnaker.orca.pipeline.WaitStage -import com.netflix.spinnaker.orca.pipeline.model.Execution -import com.netflix.spinnaker.orca.pipeline.model.Stage -import com.netflix.spinnaker.orca.pipeline.util.StageNavigator -import org.springframework.core.env.StandardEnvironment -import spock.lang.Specification -import spock.lang.Subject - -class DetermineLockTaskSpec extends Specification { - - def config = new LockingConfigurationProperties(new SpringDynamicConfigService(environment: new StandardEnvironment())) - - @Subject DetermineLockTask task = new DetermineLockTask( - new StageNavigator( - Arrays.asList( - new WaitStage(), - new AcquireLockStage(), - new ReleaseLockStage())), - config) - - def setup() { - config.setEnabled(true) - config.setLearningMode(false) - } - - def "should determine the lock when lock values explicitly provided"() { - given: - def exec = new Execution(Execution.ExecutionType.PIPELINE, app) - def stage = new Stage(exec, ReleaseLockStage.PIPELINE_TYPE, [ - lock: [ - lockName: lockName, - lockHolder: lockHolder, - lockValue: [ - type: 'pipeline', - application: app, - id: lockId]]]) - - when: - def result = task.execute(stage) - - then: - result.status == ExecutionStatus.SUCCEEDED - result.context.lock.lockName == lockName - - where: - app = 'fooapp' - lockName = 'lock' - lockId = 'fooLock' - lockHolder = 'holder' - } - - def "should determine the lock from a previous stage"() { - given: - def exec = new Execution(Execution.ExecutionType.PIPELINE, app) - def acquire = new Stage(exec, AcquireLockStage.PIPELINE_TYPE, [ - refId: 'acquireLock', - lock: [ - lockName: lockName, - lockHolder: lockHolder, - lockValue: [ - type: 'pipeline', - application: app, - id: lockId]]]) - - def wait = new Stage(exec, WaitStage.STAGE_TYPE, [ - refId: 'wait', - requisiteStageRefIds: [acquire.refId] - ]) - - def release = new Stage(exec, ReleaseLockStage.PIPELINE_TYPE, [ - refId: 'releaseLock', - requisiteStageRefIds: [wait.refId] - ]) - exec.stages.addAll(Arrays.asList(acquire, wait, release)) - - when: - def result = task.execute(release) - - then: - result.status == ExecutionStatus.SUCCEEDED - result.context.lock.lockName == lockName - - where: - app = 'fooapp' - lockName = 'lock' - lockId = 'fooLock' - lockHolder = 'holder' - } - - def "should fail if unable to determine lock from a previous stage"() { - given: - def exec = new Execution(Execution.ExecutionType.PIPELINE, app) - - def release = new Stage(exec, ReleaseLockStage.PIPELINE_TYPE, [ - refId: 'releaseLock', - ]) - exec.stages.addAll(Arrays.asList(release)) - - when: - task.execute(release) - - then: - thrown(IllegalStateException) - - where: - app = 'fooapp' - } - - def "should ignore failure to determine the lock from a previous stage when locking disabled or in learning mode"() { - given: - config.learningMode = learningMode - config.enabled = lockingEnabled - def exec = new Execution(Execution.ExecutionType.PIPELINE, app) - - def release = new Stage(exec, ReleaseLockStage.PIPELINE_TYPE, [ - refId: 'releaseLock' - ]) - exec.stages.addAll(Arrays.asList(release)) - - when: - def result = task.execute(release) - - then: - result.status == ExecutionStatus.SUCCEEDED - result.context.lock.lockName == 'unknown' - result.context.lock.lockHolder == 'unknown' - result.context.lock.lockValue.application == app - result.context.lock.lockValue.type == exec.type.toString() - result.context.lock.lockValue.id == exec.id - - - where: - app = 'fooapp' - - lockingEnabled | learningMode - false | false - false | true - true | true - } -} diff --git a/orca-redis/src/main/java/com/netflix/spinnaker/orca/locks/AbstractRedisLockManager.java b/orca-redis/src/main/java/com/netflix/spinnaker/orca/locks/AbstractRedisLockManager.java deleted file mode 100644 index d92e03dda5..0000000000 --- a/orca-redis/src/main/java/com/netflix/spinnaker/orca/locks/AbstractRedisLockManager.java +++ /dev/null @@ -1,215 +0,0 @@ -package com.netflix.spinnaker.orca.locks; - -import static java.util.Arrays.asList; -import static net.logstash.logback.argument.StructuredArguments.kv; - -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.function.Consumer; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public abstract class AbstractRedisLockManager implements LockManager { - - private static final int LOCK_VALUE_APPLICATION_IDX = 0; - private static final int LOCK_VALUE_TYPE_IDX = 1; - private static final int LOCK_VALUE_IDX = 2; - - private final LockingConfigurationProperties lockingConfigurationProperties; - private final Logger log = LoggerFactory.getLogger(getClass()); - - AbstractRedisLockManager(LockingConfigurationProperties lockingConfigurationProperties) { - this.lockingConfigurationProperties = lockingConfigurationProperties; - } - - public abstract void acquireLock( - String lockName, LockValue lockValue, String lockHolder, int ttlSeconds) - throws LockFailureException; - - public abstract void extendLock(String lockName, LockValue lockValue, int ttlSeconds) - throws LockFailureException; - - public abstract void releaseLock(String lockName, LockValue lockValue, String lockHolder); - - static class LockOperation { - static LockOperation acquire( - String lockName, LockValue lockValue, String lockHolder, int ttlSeconds) { - return new LockOperation("acquireLock", lockName, lockValue, lockHolder, ttlSeconds); - } - - static LockOperation extend(String lockName, LockValue lockValue, int ttlSeconds) { - return new LockOperation("extendLock", lockName, lockValue, null, ttlSeconds); - } - - static LockOperation release(String lockName, LockValue lockValue, String lockHolder) { - return new LockOperation("releaseLock", lockName, lockValue, lockHolder, -1); - } - - final String operationName; - final String lockName; - final LockValue lockValue; - final String lockHolder; - final int ttlSeconds; - - LockOperation( - String operationName, - String lockName, - LockValue lockValue, - String lockHolder, - int ttlSeconds) { - this.operationName = Objects.requireNonNull(operationName); - this.lockName = Objects.requireNonNull(lockName); - this.lockValue = Objects.requireNonNull(lockValue); - this.lockHolder = lockHolder; - this.ttlSeconds = ttlSeconds; - } - - List key() { - return asList(getLockKey(lockName)); - } - - List acquireArgs() { - return asList( - lockValue.getApplication(), - lockValue.getType(), - lockValue.getId(), - lockHolder, - Integer.toString(ttlSeconds)); - } - - List extendArgs() { - return asList( - lockValue.getApplication(), - lockValue.getType(), - lockValue.getId(), - Integer.toString(ttlSeconds)); - } - - List releaseArgs() { - return asList(lockValue.getApplication(), lockValue.getType(), lockValue.getId(), lockHolder); - } - } - - void checkResult(LockOperation op, List result) { - final LockValue currentLockValue = buildResultLockValue(result); - if (!(op.lockValue.equals(currentLockValue))) { - throw new LockFailureException(op.lockName, currentLockValue); - } - } - - LockValue buildResultLockValue(List result) { - if (result == null || result.size() < 3) { - throw new IllegalStateException("Unexpected result from redis: " + result); - } - if (result.stream().allMatch(Objects::isNull)) { - return null; - } - return new LockValue( - result.get(LOCK_VALUE_APPLICATION_IDX), - result.get(LOCK_VALUE_TYPE_IDX), - result.get(LOCK_VALUE_IDX)); - } - - void withLockingConfiguration( - LockOperation lockOperation, Consumer lockManagementOperation) - throws LockFailureException { - if (!lockingConfigurationProperties.isEnabled()) { - return; - } - try { - lockManagementOperation.accept(lockOperation); - } catch (Throwable t) { - if (t instanceof LockFailureException) { - LockFailureException lfe = (LockFailureException) t; - Optional currentLockValue = Optional.ofNullable(lfe.getCurrentLockValue()); - log.debug( - "LockFailureException during {} for lock {} currently held by {} {} {} requested by {} {} {} {}", - kv("operationName", lockOperation.operationName), - kv("lockName", lockOperation.lockName), - kv( - "currentLockValue.application", - currentLockValue.map(LockValue::getApplication).orElse(null)), - kv("currentLockValue.type", currentLockValue.map(LockValue::getType).orElse(null)), - kv("currentLockValue.id", currentLockValue.map(LockValue::getId).orElse(null)), - kv("requestLockValue.application", lockOperation.lockValue.getApplication()), - kv("requestLockValue.type", lockOperation.lockValue.getType()), - kv("requestLockValue.id", lockOperation.lockValue.getId()), - kv( - "requestLockHolder", - Optional.ofNullable(lockOperation.lockHolder).orElse("UNSPECIFIED")), - lfe); - if (lockingConfigurationProperties.isLearningMode()) { - return; - } - throw lfe; - } else { - log.debug( - "Exception during {} for lock {} requested by {} {} {} {}", - kv("operationName", lockOperation.operationName), - kv("operationName", lockOperation.lockName), - kv("requestLockValue.application", lockOperation.lockValue.getApplication()), - kv("requestLockValue.type", lockOperation.lockValue.getType()), - kv("requestLockValue.id", lockOperation.lockValue.getId()), - kv( - "requestLockHolder", - Optional.ofNullable(lockOperation.lockHolder).orElse("UNSPECIFIED")), - t); - if (lockingConfigurationProperties.isLearningMode()) { - return; - } - - if (t instanceof RuntimeException) { - throw (RuntimeException) t; - } - throw new RuntimeException("Exception in RedisLockManager", t); - } - } - } - - static String getLockKey(String lockName) { - return "{namedlock}:" + lockName; - } - - static final String ACQUIRE_LOCK = - "" - + "local lockKey, lockValueApplication, lockValueType, lockValue, holderHashKey, ttlSeconds = " - + " KEYS[1], ARGV[1], ARGV[2], ARGV[3], 'lockHolder.' .. ARGV[4], tonumber(ARGV[5]);" - + "if redis.call('exists', lockKey) == 1 then" - + " if not (redis.call('hget', lockKey, 'lockValueApplication') == lockValueApplication and " - + " redis.call('hget', lockKey, 'lockValueType') == lockValueType and" - + " redis.call('hget', lockKey, 'lockValue') == lockValue) then" - + " return redis.call('hmget', lockKey, 'lockValueApplication', 'lockValueType', 'lockValue');" - + " end;" - + "end;" - + "redis.call('hmset', lockKey, 'lockValueApplication', lockValueApplication, " - + " 'lockValueType', lockValueType, 'lockValue', lockValue, holderHashKey, 'true');" - + "redis.call('expire', lockKey, ttlSeconds);" - + "return {lockValueApplication, lockValueType, lockValue};"; - - static final String EXTEND_LOCK = - "" - + "local lockKey, lockValueApplication, lockValueType, lockValue, ttlSeconds = " - + " KEYS[1], ARGV[1], ARGV[2], ARGV[3], tonumber(ARGV[4]);" - + "if not (redis.call('hget', lockKey, 'lockValueApplication') == lockValueApplication and " - + " redis.call('hget', lockKey, 'lockValueType') == lockValueType and" - + " redis.call('hget', lockKey, 'lockValue') == lockValue) then" - + " return redis.call('hmget', lockKey, 'lockValueApplication', 'lockValueType', 'lockValue');" - + "end;" - + "redis.call('expire', lockKey, ttlSeconds);" - + "return {lockValueApplication, lockValueType, lockValue};"; - - static final String RELEASE_LOCK = - "" - + "local lockKey, lockValueApplication, lockValueType, lockValue, holderHashKey = " - + " KEYS[1], ARGV[1], ARGV[2], ARGV[3], 'lockHolder.' .. ARGV[4];" - + "if (redis.call('hget', lockKey, 'lockValueApplication') == lockValueApplication and " - + " redis.call('hget', lockKey, 'lockValueType') == lockValueType and" - + " redis.call('hget', lockKey, 'lockValue') == lockValue) then" - + " redis.call('hdel', lockKey, holderHashKey);" - + " if (redis.call('hlen', lockKey) == 3) then" - + " redis.call('del', lockKey);" - + " end;" - + "end;" - + "return 1;"; -} diff --git a/orca-redis/src/main/java/com/netflix/spinnaker/orca/locks/RedisClusterLockManager.java b/orca-redis/src/main/java/com/netflix/spinnaker/orca/locks/RedisClusterLockManager.java deleted file mode 100644 index 2e9c1689a7..0000000000 --- a/orca-redis/src/main/java/com/netflix/spinnaker/orca/locks/RedisClusterLockManager.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.netflix.spinnaker.orca.locks; - -import java.util.List; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.stereotype.Component; -import redis.clients.jedis.JedisCluster; - -@Component -@ConditionalOnProperty(value = "redis.cluster-enabled") -public class RedisClusterLockManager extends AbstractRedisLockManager { - - private final JedisCluster cluster; - - @Autowired - public RedisClusterLockManager(JedisCluster cluster, LockingConfigurationProperties properties) { - super(properties); - this.cluster = cluster; - } - - @Override - @SuppressWarnings("unchecked") - public void acquireLock(String lockName, LockValue lockValue, String lockHolder, int ttlSeconds) - throws LockFailureException { - withLockingConfiguration( - LockOperation.acquire(lockName, lockValue, lockHolder, ttlSeconds), - (op) -> { - final List result = - (List) cluster.eval(ACQUIRE_LOCK, op.key(), op.acquireArgs()); - checkResult(op, result); - }); - } - - @Override - @SuppressWarnings("unchecked") - public void extendLock(String lockName, LockValue lockValue, int ttlSeconds) - throws LockFailureException { - withLockingConfiguration( - LockOperation.extend(lockName, lockValue, ttlSeconds), - (op) -> { - final List result = - (List) cluster.eval(EXTEND_LOCK, op.key(), op.extendArgs()); - checkResult(op, result); - }); - } - - @Override - public void releaseLock(String lockName, LockValue lockValue, String lockHolder) { - withLockingConfiguration( - LockOperation.release(lockName, lockValue, lockHolder), - (op) -> cluster.eval(RELEASE_LOCK, op.key(), op.releaseArgs())); - } -} diff --git a/orca-redis/src/main/java/com/netflix/spinnaker/orca/locks/RedisLockManager.java b/orca-redis/src/main/java/com/netflix/spinnaker/orca/locks/RedisLockManager.java deleted file mode 100644 index a4a5e7b5dc..0000000000 --- a/orca-redis/src/main/java/com/netflix/spinnaker/orca/locks/RedisLockManager.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright 2018 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.netflix.spinnaker.orca.locks; - -import com.netflix.spinnaker.kork.jedis.RedisClientDelegate; -import com.netflix.spinnaker.kork.jedis.RedisClientSelector; -import java.util.List; -import java.util.function.Consumer; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.stereotype.Component; -import redis.clients.jedis.ScriptingCommands; - -@Component -@ConditionalOnProperty( - value = "redis.cluster-enabled", - havingValue = "false", - matchIfMissing = true) -public class RedisLockManager extends AbstractRedisLockManager { - - private final RedisClientDelegate redisClientDelegate; - - @Autowired - public RedisLockManager( - RedisClientSelector redisClientSelector, - LockingConfigurationProperties lockingConfigurationProperties) { - super(lockingConfigurationProperties); - - this.redisClientDelegate = redisClientSelector.primary("default"); - if (!redisClientDelegate.supportsScripting()) { - throw new IllegalArgumentException( - "Requires RedisClientDelegate that supports scripting but got " + redisClientDelegate); - } - } - - @Override - public void acquireLock(String lockName, LockValue lockValue, String lockHolder, int ttlSeconds) - throws LockFailureException { - withLockingConfiguration( - LockOperation.acquire(lockName, lockValue, lockHolder, ttlSeconds), - (op) -> { - final List result = - redisClientDelegate.withScriptingClient( - scriptingCommands -> - (List) - scriptingCommands.eval(ACQUIRE_LOCK, op.key(), op.acquireArgs())); - checkResult(op, result); - }); - } - - @Override - public void extendLock(String lockName, LockValue lockValue, int ttlSeconds) - throws LockFailureException { - withLockingConfiguration( - LockOperation.extend(lockName, lockValue, ttlSeconds), - (op) -> { - final List result = - redisClientDelegate.withScriptingClient( - scriptingCommands -> - (List) - scriptingCommands.eval(EXTEND_LOCK, op.key(), op.extendArgs())); - checkResult(op, result); - }); - } - - @Override - public void releaseLock(String lockName, LockValue lockValue, String lockHolder) { - withLockingConfiguration( - LockOperation.release(lockName, lockValue, lockHolder), - (op) -> - redisClientDelegate.withScriptingClient( - (Consumer) - scriptingCommands -> - scriptingCommands.eval(RELEASE_LOCK, op.key(), op.releaseArgs()))); - } -} diff --git a/orca-redis/src/test/groovy/com/netflix/spinnaker/orca/locks/RedisLockManagerSpec.groovy b/orca-redis/src/test/groovy/com/netflix/spinnaker/orca/locks/RedisLockManagerSpec.groovy deleted file mode 100644 index 2105a445ca..0000000000 --- a/orca-redis/src/test/groovy/com/netflix/spinnaker/orca/locks/RedisLockManagerSpec.groovy +++ /dev/null @@ -1,203 +0,0 @@ -package com.netflix.spinnaker.orca.locks - -import com.netflix.spinnaker.kork.dynamicconfig.DynamicConfigService -import com.netflix.spinnaker.kork.jedis.EmbeddedRedis -import com.netflix.spinnaker.kork.jedis.JedisClientDelegate -import com.netflix.spinnaker.kork.jedis.RedisClientSelector -import redis.clients.jedis.Jedis -import redis.clients.util.Pool -import spock.lang.Shared -import spock.lang.Specification -import spock.lang.Subject - -class RedisLockManagerSpec extends Specification { - - @Shared - EmbeddedRedis redisServer - - @Shared - Pool pool - - @Subject - RedisLockManager redisLockManager - - @Shared - DynamicConfigService dynamicConfigService = Stub(DynamicConfigService) { - isEnabled(_, _) >> { flag, defaultvalue -> defaultvalue } - getConfig(_, _, _) >> { flagtype, flag, defaultvalue -> defaultvalue } - } - - void setupSpec() { - redisServer = EmbeddedRedis.embed() - pool = redisServer.pool - } - - void cleanupSpec() { - pool.close() - redisServer.destroy() - } - - void setup() { - pool.resource.withCloseable { Jedis jedis -> jedis.flushAll() } - def cfg = new LockingConfigurationProperties(dynamicConfigService) - cfg.setLearningMode(false) - cfg.setEnabled(true) - redisLockManager = new RedisLockManager(new RedisClientSelector([new JedisClientDelegate("primaryDefault", pool)]), cfg) - } - - private LockManager.LockValue lv(String id) { - return new LockManager.LockValue('fooapp', 'pipeline', id) - } - - def "should acquire a lock if none exists"() { - when: - redisLockManager.acquireLock('foo', lv('bar'), 'baz', 1) - - then: - noExceptionThrown() - } - - def "should acquire a lock if already exists but same lockValue supplied"() { - given: - redisLockManager.acquireLock('foo', lv('bar'), 'baz', 300) - - when: - redisLockManager.acquireLock('foo', lv('bar'), 'buzz', 300) - - then: - noExceptionThrown() - } - - def "acquiring a lock should set TTL"() { - when: - redisLockManager.acquireLock('foo', lv('bar'), 'baz', 300) - - then: - pool.resource.withCloseable { Jedis jedis -> - jedis.ttl(RedisLockManager.getLockKey('foo')) > 290 - } - } - - def "reacquiring a lock should reset TTL"() { - given: - redisLockManager.acquireLock('foo', lv('bar'), 'baz', 100) - - when: - redisLockManager.acquireLock('foo', lv('bar'), 'baz', 300) - - then: - pool.resource.withCloseable { Jedis jedis -> - jedis.ttl(RedisLockManager.getLockKey('foo')) > 290 - } - } - - def "extending a lock should reset TTL"() { - given: - redisLockManager.acquireLock('foo', lv('bar'), 'baz', 100) - - when: - redisLockManager.extendLock('foo', lv('bar'), 300) - - then: - pool.resource.withCloseable { Jedis jedis -> - jedis.ttl(RedisLockManager.getLockKey('foo')) > 290 - } - } - - def "should fail to extend a lock if held by a different lockValue"() { - given: - redisLockManager.acquireLock('foo', lv('bar'), 'baz', 100) - - when: - redisLockManager.extendLock('foo', lv('bazinga'), 300) - - then: - def ex = thrown(LockFailureException) - ex.currentLockValue.id == 'bar' - } - - def "should fail to extend a lock if held by a different application"() { - given: - redisLockManager.acquireLock('foo', lv('bar'), 'baz', 100) - - when: - redisLockManager.extendLock('foo', new LockManager.LockValue('fooapp2', 'pipeline', 'bar'), 300) - - then: - def ex = thrown(LockFailureException) - ex.currentLockValue.application == 'fooapp' - ex.currentLockValue.id == 'bar' - } - - def "should fail to extend a lock if held by a different lockValueType"() { - given: - redisLockManager.acquireLock('foo', lv('bar'), 'baz', 100) - - when: - redisLockManager.extendLock('foo', new LockManager.LockValue('fooapp', 'orchestration', 'bar'), 300) - - then: - def ex = thrown(LockFailureException) - ex.currentLockValue.type == 'pipeline' - ex.currentLockValue.id == 'bar' - } - - def "should fail to extend an unknown lock"() { - when: - redisLockManager.extendLock('foo', lv('bazinga'), 300) - - then: - def ex = thrown(LockFailureException) - ex.currentLockValue == null - - } - - def "should fail to acquire a lock if already held with a different lockValue"() { - given: - redisLockManager.acquireLock('foo', lv('bar'), 'baz', 300) - - when: - redisLockManager.acquireLock('foo', lv('bazinga'), 'baz', 300) - - then: - def ex = thrown(LockFailureException) - ex.currentLockValue.id == 'bar' - } - - def "should be able to release non-existant lock"() { - when: - redisLockManager.releaseLock('foo', lv('bar'), 'baz') - - then: - noExceptionThrown() - } - - def "releasing only lock holder should free the lock"() { - given: - redisLockManager.acquireLock('foo', lv('bar'), 'baz', 300) - - when: - redisLockManager.releaseLock('foo', lv('bar'), 'baz') - - then: - pool.resource.withCloseable { Jedis jedis -> - !jedis.exists(RedisLockManager.getLockKey('foo')) - } - } - - def "releasing one of many lockHolders doesn't free the lock"() { - given: - def lockvalue = lv('bar') - redisLockManager.acquireLock('foo', lockvalue, 'baz', 300) - redisLockManager.acquireLock('foo', lockvalue, 'buzz', 300) - redisLockManager.releaseLock('foo', lockvalue, 'buzz') - - when: - redisLockManager.acquireLock('foo', lv('bazinga'), 'bizz', 300) - - then: - def ex = thrown(LockFailureException) - ex.currentLockValue.id == 'bar' - } - -} diff --git a/orca-web/src/test/groovy/com/netflix/spinnaker/orca/MainSpec.java b/orca-web/src/test/groovy/com/netflix/spinnaker/orca/MainSpec.java index 90a0b1a1b1..d24cb1ab02 100644 --- a/orca-web/src/test/groovy/com/netflix/spinnaker/orca/MainSpec.java +++ b/orca-web/src/test/groovy/com/netflix/spinnaker/orca/MainSpec.java @@ -16,7 +16,6 @@ package com.netflix.spinnaker.orca; -import com.netflix.spinnaker.orca.locks.LockManager; import com.netflix.spinnaker.orca.notifications.NotificationClusterLock; import com.netflix.spinnaker.orca.pipeline.persistence.ExecutionRepository; import org.junit.Test; @@ -34,8 +33,6 @@ public class MainSpec { @MockBean ExecutionRepository executionRepository; - @MockBean LockManager lockManager; - @MockBean NotificationClusterLock notificationClusterLock; @Test