From be0098eb153d9bc61635f33ec76aeb48abde14ab Mon Sep 17 00:00:00 2001 From: Michael Rose Date: Tue, 17 Jun 2014 19:15:31 -0600 Subject: [PATCH 1/6] K/V pairs can be passed in to next ASG and autodeploy processes --- .../asgard/AutoScalingController.groovy | 3 +- .../netflix/asgard/ClusterController.groovy | 16 +++++++++- .../asgard/DeploymentController.groovy | 2 +- .../asgard/AwsAutoScalingService.groovy | 4 +-- .../netflix/asgard/DeploymentService.groovy | 5 +-- .../asgard/LaunchTemplateService.groovy | 4 +-- .../deployment/DeploymentActivities.groovy | 2 +- .../DeploymentActivitiesImpl.groovy | 4 +-- .../deployment/DeploymentWorkflow.groovy | 3 +- ...ploymentWorkflowDescriptionTemplate.groovy | 3 +- .../deployment/DeploymentWorkflowImpl.groovy | 10 +++--- .../deployment/StartDeploymentRequest.groovy | 1 + .../netflix/asgard/model/LaunchContext.groovy | 5 +++ .../asgard/push/CommonPushOptions.groovy | 1 + .../asgard/DeploymentControllerSpec.groovy | 9 ++++-- .../asgard/DeploymentServiceUnitSpec.groovy | 3 +- .../asgard/LaunchTemplateServiceSpec.groovy | 8 +++-- .../DeploymentActivitiesSpec.groovy | 3 +- ...mentWorkflowDescriptionTemplateSpec.groovy | 2 +- .../deployment/DeploymentWorkflowSpec.groovy | 32 ++++++++++--------- .../StartDeploymentRequestSpec.groovy | 6 ++-- 21 files changed, 82 insertions(+), 44 deletions(-) diff --git a/grails-app/controllers/com/netflix/asgard/AutoScalingController.groovy b/grails-app/controllers/com/netflix/asgard/AutoScalingController.groovy index 6204e155..ff092b09 100644 --- a/grails-app/controllers/com/netflix/asgard/AutoScalingController.groovy +++ b/grails-app/controllers/com/netflix/asgard/AutoScalingController.groovy @@ -355,8 +355,9 @@ class AutoScalingController { launchConfigTemplate.spotPrice = spotInstanceRequestService.recommendSpotPrice(userContext, instType) } boolean enableChaosMonkey = params.chaosMonkey == 'enabled' + Map userMetaData = params.userMetaData CreateAutoScalingGroupResult result = awsAutoScalingService.createLaunchConfigAndAutoScalingGroup( - userContext, groupTemplate, launchConfigTemplate, suspendedProcesses, enableChaosMonkey) + userContext, groupTemplate, launchConfigTemplate, suspendedProcesses, enableChaosMonkey, userMetaData) flash.message = result.toString() if (result.succeeded()) { redirect(action: 'show', params: [id: groupName]) diff --git a/grails-app/controllers/com/netflix/asgard/ClusterController.groovy b/grails-app/controllers/com/netflix/asgard/ClusterController.groovy index e5f69883..dd245377 100644 --- a/grails-app/controllers/com/netflix/asgard/ClusterController.groovy +++ b/grails-app/controllers/com/netflix/asgard/ClusterController.groovy @@ -266,7 +266,8 @@ Group: ${loadBalancerNames}""" instanceType: instanceType, groupName: nextGroupName, securityGroups: securityGroups, - maxStartupRetries: convertToIntOrUseDefault(params.maxStartupRetries, 5) + maxStartupRetries: convertToIntOrUseDefault(params.maxStartupRetries, 5), + metadata: convertToKeyValue("meta") ), initialTraffic: initialTraffic, minSize: minSize, @@ -300,6 +301,19 @@ Group: ${loadBalancerNames}""" value?.isInteger() ? value.toInteger() : defaultValue } + @VisibleForTesting + @PackageScope + Map convertToKeyValue(String prefix) { + Map maps = new HashMap() + + for (Map.Entry entry : params.entrySet()) { + if (entry.getKey().startsWith(prefix)) + maps.put(entry.getKey(), entry.getValue()) + } + + return maps + } + private boolean shouldAzRebalanceBeSuspended(String azRebalance, boolean lastRebalanceSuspended) { (azRebalance == null) ? lastRebalanceSuspended : (azRebalance == 'disabled') } diff --git a/grails-app/controllers/com/netflix/asgard/DeploymentController.groovy b/grails-app/controllers/com/netflix/asgard/DeploymentController.groovy index 52e6dbf5..eff58c35 100644 --- a/grails-app/controllers/com/netflix/asgard/DeploymentController.groovy +++ b/grails-app/controllers/com/netflix/asgard/DeploymentController.groovy @@ -255,7 +255,7 @@ class DeploymentController { } else { String deploymentId = deploymentService.startDeployment(userContext, startDeploymentRequest.deploymentOptions, startDeploymentRequest.lcOptions, - startDeploymentRequest.asgOptions) + startDeploymentRequest.asgOptions, startDeploymentRequest.userMetaData) render ([deploymentId: deploymentId] as JSON) } } diff --git a/grails-app/services/com/netflix/asgard/AwsAutoScalingService.groovy b/grails-app/services/com/netflix/asgard/AwsAutoScalingService.groovy index ea5a2a72..628939f6 100644 --- a/grails-app/services/com/netflix/asgard/AwsAutoScalingService.groovy +++ b/grails-app/services/com/netflix/asgard/AwsAutoScalingService.groovy @@ -1152,7 +1152,7 @@ class AwsAutoScalingService implements CacheInitializer, InitializingBean { CreateAutoScalingGroupResult createLaunchConfigAndAutoScalingGroup(UserContext userContext, AutoScalingGroup groupTemplate, LaunchConfiguration launchConfigTemplate, Collection suspendedProcesses, boolean enableChaosMonkey = false, - Task existingTask = null) { + Map userMetaData = [:], Task existingTask = null) { CreateAutoScalingGroupResult result = new CreateAutoScalingGroupResult() String groupName = groupTemplate.autoScalingGroupName @@ -1169,7 +1169,7 @@ class AwsAutoScalingService implements CacheInitializer, InitializingBean { launchConfig.securityGroups = launchTemplateService.includeDefaultSecurityGroups( launchConfigTemplate.securityGroups, groupTemplate.VPCZoneIdentifier as boolean, userContext.region) taskService.runTask(userContext, msg, { Task task -> - String userData = launchTemplateService.buildUserData(userContext, groupOptions, launchConfig) + String userData = launchTemplateService.buildUserData(userContext, groupOptions, launchConfig, userMetaData) launchConfig.userData = userData result.launchConfigName = launchConfigName result.autoScalingGroupName = groupName diff --git a/grails-app/services/com/netflix/asgard/DeploymentService.groovy b/grails-app/services/com/netflix/asgard/DeploymentService.groovy index 8e41e4f8..e94943a5 100644 --- a/grails-app/services/com/netflix/asgard/DeploymentService.groovy +++ b/grails-app/services/com/netflix/asgard/DeploymentService.groovy @@ -146,11 +146,12 @@ class DeploymentService { * @return the unique ID of the workflow execution that is starting */ public String startDeployment(UserContext userContext, DeploymentWorkflowOptions deploymentOptions, - LaunchConfigurationBeanOptions lcOverrides, AutoScalingGroupBeanOptions asgOverrides) { + LaunchConfigurationBeanOptions lcOverrides, AutoScalingGroupBeanOptions asgOverrides, + Map userMetaData) { SwfWorkflow workflow = flowService.getNewWorkflowClient(userContext, DeploymentWorkflow, new Link(EntityType.cluster, deploymentOptions.clusterName)) - workflow.client.deploy(userContext, deploymentOptions, lcOverrides, asgOverrides) + workflow.client.deploy(userContext, deploymentOptions, lcOverrides, asgOverrides, userMetaData) workflow.tags.id } diff --git a/grails-app/services/com/netflix/asgard/LaunchTemplateService.groovy b/grails-app/services/com/netflix/asgard/LaunchTemplateService.groovy index 29ade0d3..5bc56c81 100644 --- a/grails-app/services/com/netflix/asgard/LaunchTemplateService.groovy +++ b/grails-app/services/com/netflix/asgard/LaunchTemplateService.groovy @@ -79,7 +79,7 @@ class LaunchTemplateService { * @return the user data string that should be applied to the LaunchConfiguration of an auto scaling group */ String buildUserData(UserContext userContext, AutoScalingGroupBeanOptions autoScalingGroup, - LaunchConfigurationBeanOptions launchConfiguration) { + LaunchConfigurationBeanOptions launchConfiguration, Map userMetaData) { String imageId = launchConfiguration.imageId Image image = awsEc2Service.getImage(userContext, imageId) @@ -87,7 +87,7 @@ class LaunchTemplateService { AppRegistration app = applicationService.getRegisteredApplication(userContext, appName) // Wrap all the inputs in a single class so we can add more inputs later without changing the plugin interface. - LaunchContext launchContext = new LaunchContext(userContext, image, app, autoScalingGroup, launchConfiguration) + LaunchContext launchContext = new LaunchContext(userContext, image, app, autoScalingGroup, launchConfiguration, userMetaData) pluginService.advancedUserDataProvider.buildUserData(launchContext) } diff --git a/src/groovy/com/netflix/asgard/deployment/DeploymentActivities.groovy b/src/groovy/com/netflix/asgard/deployment/DeploymentActivities.groovy index 433c0ffd..04dcf4e7 100644 --- a/src/groovy/com/netflix/asgard/deployment/DeploymentActivities.groovy +++ b/src/groovy/com/netflix/asgard/deployment/DeploymentActivities.groovy @@ -60,7 +60,7 @@ interface DeploymentActivities { * @return name of the launch configuration */ String createLaunchConfigForNextAsg(UserContext userContext, AutoScalingGroupBeanOptions autoScalingGroup, - LaunchConfigurationBeanOptions launchConfiguration) + LaunchConfigurationBeanOptions launchConfiguration, Map userMetaData) /** * Creates the next ASG in the cluster based on the asgOptions but without instances. diff --git a/src/groovy/com/netflix/asgard/deployment/DeploymentActivitiesImpl.groovy b/src/groovy/com/netflix/asgard/deployment/DeploymentActivitiesImpl.groovy index 6e1c3ab1..47680b60 100644 --- a/src/groovy/com/netflix/asgard/deployment/DeploymentActivitiesImpl.groovy +++ b/src/groovy/com/netflix/asgard/deployment/DeploymentActivitiesImpl.groovy @@ -91,9 +91,9 @@ class DeploymentActivitiesImpl implements DeploymentActivities { @Override String createLaunchConfigForNextAsg(UserContext userContext, AutoScalingGroupBeanOptions autoScalingGroup, - LaunchConfigurationBeanOptions launchConfiguration) { + LaunchConfigurationBeanOptions launchConfiguration, Map userMetaData) { launchConfiguration.userData = launchTemplateService.buildUserData(userContext, autoScalingGroup, - launchConfiguration) + launchConfiguration, userMetaData) awsAutoScalingService.createLaunchConfiguration(userContext, launchConfiguration, new Task()) launchConfiguration.launchConfigurationName } diff --git a/src/groovy/com/netflix/asgard/deployment/DeploymentWorkflow.groovy b/src/groovy/com/netflix/asgard/deployment/DeploymentWorkflow.groovy index df77bfff..399f4906 100644 --- a/src/groovy/com/netflix/asgard/deployment/DeploymentWorkflow.groovy +++ b/src/groovy/com/netflix/asgard/deployment/DeploymentWorkflow.groovy @@ -40,7 +40,8 @@ interface DeploymentWorkflow { */ @Execute(version = "1.5") void deploy(UserContext userContext, DeploymentWorkflowOptions deploymentOptions, - LaunchConfigurationBeanOptions lcOverrides, AutoScalingGroupBeanOptions asgOverrides) + LaunchConfigurationBeanOptions lcOverrides, AutoScalingGroupBeanOptions asgOverrides, + Map userMetaData) /** * @return current log history of the workflow diff --git a/src/groovy/com/netflix/asgard/deployment/DeploymentWorkflowDescriptionTemplate.groovy b/src/groovy/com/netflix/asgard/deployment/DeploymentWorkflowDescriptionTemplate.groovy index ca5d1a88..ff4f071b 100644 --- a/src/groovy/com/netflix/asgard/deployment/DeploymentWorkflowDescriptionTemplate.groovy +++ b/src/groovy/com/netflix/asgard/deployment/DeploymentWorkflowDescriptionTemplate.groovy @@ -27,7 +27,8 @@ class DeploymentWorkflowDescriptionTemplate extends WorkflowDescriptionTemplate @Override void deploy(UserContext userContext, DeploymentWorkflowOptions deploymentOptions, - LaunchConfigurationBeanOptions lcOverrides, AutoScalingGroupBeanOptions asgOverrides) { + LaunchConfigurationBeanOptions lcOverrides, AutoScalingGroupBeanOptions asgOverrides, + Map userMetaData) { description = "Deploying new ASG to cluster '${deploymentOptions.clusterName}'" } } diff --git a/src/groovy/com/netflix/asgard/deployment/DeploymentWorkflowImpl.groovy b/src/groovy/com/netflix/asgard/deployment/DeploymentWorkflowImpl.groovy index f1710768..01930eb2 100644 --- a/src/groovy/com/netflix/asgard/deployment/DeploymentWorkflowImpl.groovy +++ b/src/groovy/com/netflix/asgard/deployment/DeploymentWorkflowImpl.groovy @@ -60,7 +60,8 @@ class DeploymentWorkflowImpl implements DeploymentWorkflow, WorkflowOperator userMetaData) { String clusterName = deploymentOptions.clusterName Promise asgDeploymentNamesPromise = promiseFor(activities.getAsgDeploymentNames(userContext, clusterName)) @@ -78,7 +79,7 @@ class DeploymentWorkflowImpl implements DeploymentWorkflow, WorkflowOperator startDeployment(userContext, deploymentOptions, asgDeploymentNames, nextAsgTemplate, nextLcTemplate, - runningAsgAnalyses) + userMetaData, runningAsgAnalyses) } } withCatch { Throwable e -> rollbackCause = e @@ -110,7 +111,8 @@ class DeploymentWorkflowImpl implements DeploymentWorkflow, WorkflowOperator startDeployment(UserContext userContext, DeploymentWorkflowOptions deploymentOptions, AsgDeploymentNames asgDeploymentNames, AutoScalingGroupBeanOptions nextAsgTemplate, - LaunchConfigurationBeanOptions nextLcTemplate, List> runningAsgAnalyses) { + LaunchConfigurationBeanOptions nextLcTemplate, Map userMetaData, + List> runningAsgAnalyses) { Map, Closure> stepsToOperations = [ (WaitStep): { WaitStep step -> @@ -120,7 +122,7 @@ class DeploymentWorkflowImpl implements DeploymentWorkflow, WorkflowOperator status "Creating Launch Configuration '${asgDeploymentNames.nextLaunchConfigName}'." Promise launchConfigCreated = promiseFor( - activities.createLaunchConfigForNextAsg(userContext, nextAsgTemplate, nextLcTemplate)) + activities.createLaunchConfigForNextAsg(userContext, nextAsgTemplate, nextLcTemplate, userMetaData)) waitFor(launchConfigCreated) { status "Creating Auto Scaling Group '${asgDeploymentNames.nextAsgName}' \ initially with 0 instances." diff --git a/src/groovy/com/netflix/asgard/deployment/StartDeploymentRequest.groovy b/src/groovy/com/netflix/asgard/deployment/StartDeploymentRequest.groovy index 599688d7..8d00a406 100644 --- a/src/groovy/com/netflix/asgard/deployment/StartDeploymentRequest.groovy +++ b/src/groovy/com/netflix/asgard/deployment/StartDeploymentRequest.groovy @@ -29,6 +29,7 @@ class StartDeploymentRequest { DeploymentWorkflowOptions deploymentOptions LaunchConfigurationBeanOptions lcOptions AutoScalingGroupBeanOptions asgOptions + Map userMetaData /** * @return List of all validation errors diff --git a/src/groovy/com/netflix/asgard/model/LaunchContext.groovy b/src/groovy/com/netflix/asgard/model/LaunchContext.groovy index 6969368c..40d4c3ea 100644 --- a/src/groovy/com/netflix/asgard/model/LaunchContext.groovy +++ b/src/groovy/com/netflix/asgard/model/LaunchContext.groovy @@ -49,4 +49,9 @@ import groovy.transform.Canonical * An object similar in most ways to the launch configuration with which the deployment will be done. */ LaunchConfigurationBeanOptions launchConfiguration + + /** + * User metadata for providing to custom userdata providers + */ + Map userMetaData } diff --git a/src/groovy/com/netflix/asgard/push/CommonPushOptions.groovy b/src/groovy/com/netflix/asgard/push/CommonPushOptions.groovy index 8e62d13d..a4806c4c 100644 --- a/src/groovy/com/netflix/asgard/push/CommonPushOptions.groovy +++ b/src/groovy/com/netflix/asgard/push/CommonPushOptions.groovy @@ -29,4 +29,5 @@ import groovy.transform.Immutable String groupName List securityGroups Integer maxStartupRetries = 5 + Map metadata = [:] } diff --git a/test/unit/com/netflix/asgard/DeploymentControllerSpec.groovy b/test/unit/com/netflix/asgard/DeploymentControllerSpec.groovy index e053c441..f966a346 100644 --- a/test/unit/com/netflix/asgard/DeploymentControllerSpec.groovy +++ b/test/unit/com/netflix/asgard/DeploymentControllerSpec.groovy @@ -85,7 +85,9 @@ class DeploymentControllerSpec extends Specification { {"type": "DisableAsg", "targetAsg": "Previous"}, {"type": "Judgment", "durationMinutes": 241}, {"type": "DeleteAsg", "targetAsg": "Previous"} - ]}}""" as String + ]}, + "userMetaData": {"meta": "data"} + }""" as String } void setup() { @@ -195,8 +197,9 @@ class DeploymentControllerSpec extends Specification { subnetPurpose: "internal", terminationPolicies: ["OldestLaunchConfiguration"], tags: [], - suspendedProcesses: [] - ) + suspendedProcesses: [], + ), + [meta: "data"] ) >> '123' } diff --git a/test/unit/com/netflix/asgard/DeploymentServiceUnitSpec.groovy b/test/unit/com/netflix/asgard/DeploymentServiceUnitSpec.groovy index a8fee621..16ca8fe3 100644 --- a/test/unit/com/netflix/asgard/DeploymentServiceUnitSpec.groovy +++ b/test/unit/com/netflix/asgard/DeploymentServiceUnitSpec.groovy @@ -147,9 +147,10 @@ class DeploymentServiceUnitSpec extends Specification { DeploymentWorkflowOptions deployOpts = new DeploymentWorkflowOptions(clusterName: 'Calysteral') LaunchConfigurationBeanOptions lcOpts = new LaunchConfigurationBeanOptions() AutoScalingGroupBeanOptions asgOpts = new AutoScalingGroupBeanOptions() + Map userMetaData = [meta: "data"] when: - String taskId = deploymentService.startDeployment(userContext, deployOpts, lcOpts, asgOpts) + String taskId = deploymentService.startDeployment(userContext, deployOpts, lcOpts, asgOpts, userMetaData) then: taskId == '07700900461' diff --git a/test/unit/com/netflix/asgard/LaunchTemplateServiceSpec.groovy b/test/unit/com/netflix/asgard/LaunchTemplateServiceSpec.groovy index dfa059fa..01924a33 100644 --- a/test/unit/com/netflix/asgard/LaunchTemplateServiceSpec.groovy +++ b/test/unit/com/netflix/asgard/LaunchTemplateServiceSpec.groovy @@ -138,6 +138,8 @@ class LaunchTemplateServiceSpec extends Specification { launchConfigurationName: 'hello-wassup-987654321') LaunchConfigurationBeanOptions launchConfig = new LaunchConfigurationBeanOptions( launchConfigurationName: 'hello-wassup-987654321', imageId: image.imageId) + Map userMetaData = [meta: "data"] + launchTemplateService.applicationService = Mock(ApplicationService) { getRegisteredApplication(*_) >> application } @@ -146,11 +148,11 @@ class LaunchTemplateServiceSpec extends Specification { } when: - String userData = launchTemplateService.buildUserData(userContext, asg, launchConfig) + String userData = launchTemplateService.buildUserData(userContext, asg, launchConfig, userMetaData) then: 1 * advancedUserDataProvider.buildUserData( - new LaunchContext(userContext, image, application, asg, launchConfig)) >> 'ta da!' + new LaunchContext(userContext, image, application, asg, launchConfig, userMetaData)) >> 'ta da!' userData == 'ta da!' } @@ -163,7 +165,7 @@ class LaunchTemplateServiceSpec extends Specification { String userData = launchTemplateService.buildUserDataForImage(userContext, image) then: - 1 * advancedUserDataProvider.buildUserData(new LaunchContext(userContext, image, null, null, null)) >> 'ta da!' + 1 * advancedUserDataProvider.buildUserData(new LaunchContext(userContext, image, null, null, null, null)) >> 'ta da!' userData == 'ta da!' } } diff --git a/test/unit/com/netflix/asgard/deployment/DeploymentActivitiesSpec.groovy b/test/unit/com/netflix/asgard/deployment/DeploymentActivitiesSpec.groovy index c13c5f3c..8c68e6e3 100644 --- a/test/unit/com/netflix/asgard/deployment/DeploymentActivitiesSpec.groovy +++ b/test/unit/com/netflix/asgard/deployment/DeploymentActivitiesSpec.groovy @@ -114,7 +114,8 @@ class DeploymentActivitiesSpec extends Specification { def 'should create launch config for next ASG'() { when: deploymentActivities.createLaunchConfigForNextAsg(userContext, null, - new LaunchConfigurationBeanOptions(launchConfigurationName: 'rearden_metal_pourer-20130718090004')) + new LaunchConfigurationBeanOptions(launchConfigurationName: 'rearden_metal_pourer-20130718090004'), + [meta:"data"]) then: with(mockAwsAutoScalingService) { diff --git a/test/unit/com/netflix/asgard/deployment/DeploymentWorkflowDescriptionTemplateSpec.groovy b/test/unit/com/netflix/asgard/deployment/DeploymentWorkflowDescriptionTemplateSpec.groovy index 0265379b..c59817f0 100644 --- a/test/unit/com/netflix/asgard/deployment/DeploymentWorkflowDescriptionTemplateSpec.groovy +++ b/test/unit/com/netflix/asgard/deployment/DeploymentWorkflowDescriptionTemplateSpec.groovy @@ -23,7 +23,7 @@ class DeploymentWorkflowDescriptionTemplateSpec extends Specification { DeploymentWorkflowDescriptionTemplate descriptionTemplate = new DeploymentWorkflowDescriptionTemplate() when: - descriptionTemplate.deploy(null, new DeploymentWorkflowOptions(clusterName: 'the_seaward'), null, null) + descriptionTemplate.deploy(null, new DeploymentWorkflowOptions(clusterName: 'the_seaward'), null, null, null) then: "Deploying new ASG to cluster 'the_seaward'" == descriptionTemplate.description diff --git a/test/unit/com/netflix/asgard/deployment/DeploymentWorkflowSpec.groovy b/test/unit/com/netflix/asgard/deployment/DeploymentWorkflowSpec.groovy index 69093abe..be8a6eed 100644 --- a/test/unit/com/netflix/asgard/deployment/DeploymentWorkflowSpec.groovy +++ b/test/unit/com/netflix/asgard/deployment/DeploymentWorkflowSpec.groovy @@ -52,6 +52,8 @@ class DeploymentWorkflowSpec extends Specification { securityGroups: ['sg-defec8ed', 'sg-default'], iamInstanceProfile: 'defaultIamInstanceProfile', instancePriceType: InstancePriceType.ON_DEMAND, launchConfigurationName: 'the_seaward-v003-20130626140848') + Map userMetaData = [meta: "data"] + AutoScalingGroupBeanOptions asgInputs = new AutoScalingGroupBeanOptions( availabilityZones: ['us-west2a', 'us-west2b'], minSize: 1, desiredCapacity: 3, maxSize: 4, subnetPurpose: 'internal') @@ -77,7 +79,7 @@ class DeploymentWorkflowSpec extends Specification { with(mockActivities) { 1 * getAsgDeploymentNames(userContext, 'the_seaward') >> asgDeploymentNames 1 * constructLaunchConfigForNextAsg(userContext, asgTemplate, lcInputs) >> lcTemplate - 1 * createLaunchConfigForNextAsg(userContext, asgTemplate, lcTemplate) >> 'the_seaward-v003-20130626140848' + 1 * createLaunchConfigForNextAsg(userContext, asgTemplate, lcTemplate, userMetaData) >> 'the_seaward-v003-20130626140848' 1 * createNextAsgForClusterWithoutInstances(userContext, asgTemplate) >> 'the_seaward-v003' 1 * copyScalingPolicies(userContext, asgDeploymentNames) >> 0 1 * copyScheduledActions(userContext, asgDeploymentNames) >> 0 @@ -99,7 +101,7 @@ class DeploymentWorkflowSpec extends Specification { ) when: - workflowExecuter.deploy(userContext, deploymentOptions, lcInputs, asgInputs) + workflowExecuter.deploy(userContext, deploymentOptions, lcInputs, asgInputs, userMetaData) then: workflowOperations.logHistory == ['{"step":0}', 'Waiting 10 minutes before next step.', '{"step":1}'] + @@ -141,7 +143,7 @@ class DeploymentWorkflowSpec extends Specification { ) when: - workflowExecuter.deploy(userContext, deploymentOptions, lcInputs, asgInputs) + workflowExecuter.deploy(userContext, deploymentOptions, lcInputs, asgInputs, userMetaData) then: workflowOperations.logHistory == ['{"step":0}', 'Waiting 10 minutes before next step.', '{"step":1}'] + @@ -183,7 +185,7 @@ class DeploymentWorkflowSpec extends Specification { ) when: - workflowExecuter.deploy(userContext, deploymentOptions, lcInputs, asgInputs) + workflowExecuter.deploy(userContext, deploymentOptions, lcInputs, asgInputs, userMetaData) then: workflowOperations.logHistory == ['{"step":0}'] + createAsgLog + '{"step":1}' + fullCapacityScaleUpLog + [ @@ -217,7 +219,7 @@ class DeploymentWorkflowSpec extends Specification { ) when: - workflowExecuter.deploy(userContext, deploymentOptions, lcInputs, asgInputs) + workflowExecuter.deploy(userContext, deploymentOptions, lcInputs, asgInputs, userMetaData) then: workflowOperations.logHistory == ['{"step":0}'] + createAsgLog + '{"step":1}' + canaryScaleUpLog + [ @@ -244,7 +246,7 @@ class DeploymentWorkflowSpec extends Specification { ) when: - workflowExecuter.deploy(userContext, deploymentOptions, lcInputs, asgInputs) + workflowExecuter.deploy(userContext, deploymentOptions, lcInputs, asgInputs, userMetaData) then: workflowOperations.logHistory == ['{"step":0}'] + createAsgLog + '{"step":1}' + canaryScaleUpLog + @@ -276,7 +278,7 @@ class DeploymentWorkflowSpec extends Specification { ) when: - workflowExecuter.deploy(userContext, deploymentOptions, lcInputs, asgInputs) + workflowExecuter.deploy(userContext, deploymentOptions, lcInputs, asgInputs, userMetaData) then: workflowOperations.logHistory == ['{"step":0}'] + createAsgLog + '{"step":1}' + fullCapacityScaleUpLog + [ @@ -306,7 +308,7 @@ class DeploymentWorkflowSpec extends Specification { ) when: - workflowExecuter.deploy(userContext, deploymentOptions, lcInputs, asgInputs) + workflowExecuter.deploy(userContext, deploymentOptions, lcInputs, asgInputs, userMetaData) then: workflowOperations.logHistory == ['{"step":0}'] + createAsgLog + '{"step":1}' + canaryScaleUpLog + @@ -337,7 +339,7 @@ class DeploymentWorkflowSpec extends Specification { ) when: - workflowExecuter.deploy(userContext, deploymentOptions, lcInputs, asgInputs) + workflowExecuter.deploy(userContext, deploymentOptions, lcInputs, asgInputs, userMetaData) then: workflowOperations.logHistory == ['{"step":0}'] + createAsgLog + '{"step":1}' + fullCapacityScaleUpLog + @@ -369,7 +371,7 @@ class DeploymentWorkflowSpec extends Specification { ) when: - workflowExecuter.deploy(userContext, deploymentOptions, lcInputs, asgInputs) + workflowExecuter.deploy(userContext, deploymentOptions, lcInputs, asgInputs, userMetaData) then: workflowOperations.logHistory == ['{"step":0}'] + createAsgLog + '{"step":1}' + canaryScaleUpLog + @@ -407,7 +409,7 @@ class DeploymentWorkflowSpec extends Specification { ) when: - workflowExecuter.deploy(userContext, deploymentOptions, lcInputs, asgInputs) + workflowExecuter.deploy(userContext, deploymentOptions, lcInputs, asgInputs, userMetaData) then: workflowOperations.logHistory == ['{"step":0}'] + createAsgLog + '{"step":1}' + canaryScaleUpLog + @@ -446,7 +448,7 @@ class DeploymentWorkflowSpec extends Specification { ) when: - workflowExecuter.deploy(userContext, deploymentOptions, lcInputs, asgInputs) + workflowExecuter.deploy(userContext, deploymentOptions, lcInputs, asgInputs, userMetaData) then: workflowOperations.logHistory == ['{"step":0}'] + createAsgLog + '{"step":1}' + fullCapacityScaleUpLog + [ @@ -488,7 +490,7 @@ class DeploymentWorkflowSpec extends Specification { ) when: - workflowExecuter.deploy(userContext, deploymentOptions, lcInputs, asgInputs) + workflowExecuter.deploy(userContext, deploymentOptions, lcInputs, asgInputs, userMetaData) then: workflowOperations.logHistory == ['{"step":0}'] + createAsgLog + '{"step":1}' + fullCapacityScaleUpLog + [ @@ -532,7 +534,7 @@ class DeploymentWorkflowSpec extends Specification { ) when: - workflowExecuter.deploy(userContext, deploymentOptions, lcInputs, asgInputs) + workflowExecuter.deploy(userContext, deploymentOptions, lcInputs, asgInputs, userMetaData) then: workflowOperations.logHistory == ['{"step":0}'] + createAsgLog + '{"step":1}' + fullCapacityScaleUpLog + [ @@ -568,7 +570,7 @@ class DeploymentWorkflowSpec extends Specification { ) when: - workflowExecuter.deploy(userContext, deploymentOptions, lcInputs, asgInputs) + workflowExecuter.deploy(userContext, deploymentOptions, lcInputs, asgInputs, userMetaData) then: workflowOperations.logHistory == ['{"step":0}'] + createAsgLog + '{"step":1}' + fullCapacityScaleUpLog + [ diff --git a/test/unit/com/netflix/asgard/deployment/StartDeploymentRequestSpec.groovy b/test/unit/com/netflix/asgard/deployment/StartDeploymentRequestSpec.groovy index 409fd73c..f1ac985b 100644 --- a/test/unit/com/netflix/asgard/deployment/StartDeploymentRequestSpec.groovy +++ b/test/unit/com/netflix/asgard/deployment/StartDeploymentRequestSpec.groovy @@ -70,6 +70,8 @@ class StartDeploymentRequestSpec extends Specification { subnetPurpose: "internal", terminationPolicies: ["OldestLaunchConfiguration"], suspendedProcesses: [AutoScalingProcessType.AddToLoadBalancer]) + , + [meta: "data"] ) String json = '{"deploymentOptions":{"clusterName":"helloworld","notificationDestination":"jdoe@netflix.com",' + @@ -82,7 +84,6 @@ class StartDeploymentRequestSpec extends Specification { '{"type":"DisableAsg","targetAsg":"Previous"},' + '{"type":"DeleteAsg","targetAsg":"Previous"}' + ']},' + - '"lcOptions":{"launchConfigurationName":null,"imageId":"ami-12345678",' + '"keyName":"nf-test-keypair-a","securityGroups":["sg-12345678"],' + '"userData":"#!/bin/bash","instanceType":"m1.large","kernelId":"123","ramdiskId":"abc",' + @@ -94,7 +95,8 @@ class StartDeploymentRequestSpec extends Specification { '"loadBalancerNames":["helloworld--frontend"],"healthCheckType":"EC2","healthCheckGracePeriod":600,' + '"placementGroup":null,"subnetPurpose":"internal",' + '"terminationPolicies":["OldestLaunchConfiguration"],"tags":null,' + - '"suspendedProcesses":["AddToLoadBalancer"]}}' + '"suspendedProcesses":["AddToLoadBalancer"]},' + + '"userMetaData":{"meta":"data"}}' void 'should convert a StartDeploymentRequest to JSON'() { expect: From 7c2da33793bcf42b90ad6d72a7a01cc35f184b01 Mon Sep 17 00:00:00 2001 From: Michael Rose Date: Tue, 17 Jun 2014 23:42:27 -0600 Subject: [PATCH 2/6] GroupCreateOperation: updated for userMetadata --- .../controllers/com/netflix/asgard/ClusterController.groovy | 2 +- src/groovy/com/netflix/asgard/push/CommonPushOptions.groovy | 2 +- src/groovy/com/netflix/asgard/push/GroupCreateOperation.groovy | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/grails-app/controllers/com/netflix/asgard/ClusterController.groovy b/grails-app/controllers/com/netflix/asgard/ClusterController.groovy index b7178066..2eb4bdb7 100644 --- a/grails-app/controllers/com/netflix/asgard/ClusterController.groovy +++ b/grails-app/controllers/com/netflix/asgard/ClusterController.groovy @@ -267,7 +267,7 @@ Group: ${loadBalancerNames}""" groupName: nextGroupName, securityGroups: securityGroups, maxStartupRetries: convertToIntOrUseDefault(params.maxStartupRetries, 5), - metadata: params.userMetaData + userMetadata: params.userMetaData ), initialTraffic: initialTraffic, minSize: minSize, diff --git a/src/groovy/com/netflix/asgard/push/CommonPushOptions.groovy b/src/groovy/com/netflix/asgard/push/CommonPushOptions.groovy index a4806c4c..3e789d93 100644 --- a/src/groovy/com/netflix/asgard/push/CommonPushOptions.groovy +++ b/src/groovy/com/netflix/asgard/push/CommonPushOptions.groovy @@ -29,5 +29,5 @@ import groovy.transform.Immutable String groupName List securityGroups Integer maxStartupRetries = 5 - Map metadata = [:] + Map userMetadata = [:] } diff --git a/src/groovy/com/netflix/asgard/push/GroupCreateOperation.groovy b/src/groovy/com/netflix/asgard/push/GroupCreateOperation.groovy index 748423ac..b0c99bfb 100644 --- a/src/groovy/com/netflix/asgard/push/GroupCreateOperation.groovy +++ b/src/groovy/com/netflix/asgard/push/GroupCreateOperation.groovy @@ -92,7 +92,8 @@ class GroupCreateOperation extends AbstractPushOperation { } CreateAutoScalingGroupResult result = awsAutoScalingService.createLaunchConfigAndAutoScalingGroup( - options.common.userContext, groupTemplate, launchConfigTemplate, suspendedProcesses, false, task) + options.common.userContext, groupTemplate, launchConfigTemplate, suspendedProcesses, false, + options.common.userMetadata, task) log.debug """GroupCreateOperation.start for Cluster '${clusterName}' Group created with Load Balancers: \ ${groupTemplate.loadBalancerNames} and result ${result}""" task.log(result.toString()) From 7d4ffa4e2aefa09a92c48ae174788f9373aa7ea0 Mon Sep 17 00:00:00 2001 From: Michael Rose Date: Wed, 18 Jun 2014 00:17:54 -0600 Subject: [PATCH 3/6] Fixing codenarc violations --- .../com/netflix/asgard/AutoScalingController.groovy | 3 ++- .../services/com/netflix/asgard/LaunchTemplateService.groovy | 3 ++- .../netflix/asgard/deployment/DeploymentWorkflowImpl.groovy | 3 ++- test/unit/com/netflix/asgard/LaunchTemplateServiceSpec.groovy | 3 ++- .../netflix/asgard/deployment/DeploymentWorkflowSpec.groovy | 3 ++- 5 files changed, 10 insertions(+), 5 deletions(-) diff --git a/grails-app/controllers/com/netflix/asgard/AutoScalingController.groovy b/grails-app/controllers/com/netflix/asgard/AutoScalingController.groovy index ff092b09..a2b9b390 100644 --- a/grails-app/controllers/com/netflix/asgard/AutoScalingController.groovy +++ b/grails-app/controllers/com/netflix/asgard/AutoScalingController.groovy @@ -357,7 +357,8 @@ class AutoScalingController { boolean enableChaosMonkey = params.chaosMonkey == 'enabled' Map userMetaData = params.userMetaData CreateAutoScalingGroupResult result = awsAutoScalingService.createLaunchConfigAndAutoScalingGroup( - userContext, groupTemplate, launchConfigTemplate, suspendedProcesses, enableChaosMonkey, userMetaData) + userContext, groupTemplate, launchConfigTemplate, suspendedProcesses, enableChaosMonkey, + userMetaData) flash.message = result.toString() if (result.succeeded()) { redirect(action: 'show', params: [id: groupName]) diff --git a/grails-app/services/com/netflix/asgard/LaunchTemplateService.groovy b/grails-app/services/com/netflix/asgard/LaunchTemplateService.groovy index 5bc56c81..386644f6 100644 --- a/grails-app/services/com/netflix/asgard/LaunchTemplateService.groovy +++ b/grails-app/services/com/netflix/asgard/LaunchTemplateService.groovy @@ -87,7 +87,8 @@ class LaunchTemplateService { AppRegistration app = applicationService.getRegisteredApplication(userContext, appName) // Wrap all the inputs in a single class so we can add more inputs later without changing the plugin interface. - LaunchContext launchContext = new LaunchContext(userContext, image, app, autoScalingGroup, launchConfiguration, userMetaData) + LaunchContext launchContext = new LaunchContext(userContext, image, app, autoScalingGroup, launchConfiguration, + userMetaData) pluginService.advancedUserDataProvider.buildUserData(launchContext) } diff --git a/src/groovy/com/netflix/asgard/deployment/DeploymentWorkflowImpl.groovy b/src/groovy/com/netflix/asgard/deployment/DeploymentWorkflowImpl.groovy index 01930eb2..14f65b3f 100644 --- a/src/groovy/com/netflix/asgard/deployment/DeploymentWorkflowImpl.groovy +++ b/src/groovy/com/netflix/asgard/deployment/DeploymentWorkflowImpl.groovy @@ -122,7 +122,8 @@ class DeploymentWorkflowImpl implements DeploymentWorkflow, WorkflowOperator status "Creating Launch Configuration '${asgDeploymentNames.nextLaunchConfigName}'." Promise launchConfigCreated = promiseFor( - activities.createLaunchConfigForNextAsg(userContext, nextAsgTemplate, nextLcTemplate, userMetaData)) + activities.createLaunchConfigForNextAsg(userContext, nextAsgTemplate, nextLcTemplate, + userMetaData)) waitFor(launchConfigCreated) { status "Creating Auto Scaling Group '${asgDeploymentNames.nextAsgName}' \ initially with 0 instances." diff --git a/test/unit/com/netflix/asgard/LaunchTemplateServiceSpec.groovy b/test/unit/com/netflix/asgard/LaunchTemplateServiceSpec.groovy index 01924a33..9c00459b 100644 --- a/test/unit/com/netflix/asgard/LaunchTemplateServiceSpec.groovy +++ b/test/unit/com/netflix/asgard/LaunchTemplateServiceSpec.groovy @@ -165,7 +165,8 @@ class LaunchTemplateServiceSpec extends Specification { String userData = launchTemplateService.buildUserDataForImage(userContext, image) then: - 1 * advancedUserDataProvider.buildUserData(new LaunchContext(userContext, image, null, null, null, null)) >> 'ta da!' + 1 * advancedUserDataProvider.buildUserData(new LaunchContext(userContext, image, null, null, null, null)) >> + 'ta da!' userData == 'ta da!' } } diff --git a/test/unit/com/netflix/asgard/deployment/DeploymentWorkflowSpec.groovy b/test/unit/com/netflix/asgard/deployment/DeploymentWorkflowSpec.groovy index be8a6eed..ff835dec 100644 --- a/test/unit/com/netflix/asgard/deployment/DeploymentWorkflowSpec.groovy +++ b/test/unit/com/netflix/asgard/deployment/DeploymentWorkflowSpec.groovy @@ -79,7 +79,8 @@ class DeploymentWorkflowSpec extends Specification { with(mockActivities) { 1 * getAsgDeploymentNames(userContext, 'the_seaward') >> asgDeploymentNames 1 * constructLaunchConfigForNextAsg(userContext, asgTemplate, lcInputs) >> lcTemplate - 1 * createLaunchConfigForNextAsg(userContext, asgTemplate, lcTemplate, userMetaData) >> 'the_seaward-v003-20130626140848' + 1 * createLaunchConfigForNextAsg(userContext, asgTemplate, lcTemplate, userMetaData) >> + 'the_seaward-v003-20130626140848' 1 * createNextAsgForClusterWithoutInstances(userContext, asgTemplate) >> 'the_seaward-v003' 1 * copyScalingPolicies(userContext, asgDeploymentNames) >> 0 1 * copyScheduledActions(userContext, asgDeploymentNames) >> 0 From 27cf9e09c0431e4e99488c72a88e241949c67fab Mon Sep 17 00:00:00 2001 From: Michael Rose Date: Wed, 18 Jun 2014 00:36:53 -0600 Subject: [PATCH 4/6] ASG Controller Spec updated --- test/unit/com/netflix/asgard/AutoScalingControllerSpec.groovy | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/unit/com/netflix/asgard/AutoScalingControllerSpec.groovy b/test/unit/com/netflix/asgard/AutoScalingControllerSpec.groovy index 5d62ad68..f3796a3d 100644 --- a/test/unit/com/netflix/asgard/AutoScalingControllerSpec.groovy +++ b/test/unit/com/netflix/asgard/AutoScalingControllerSpec.groovy @@ -380,8 +380,8 @@ class AutoScalingControllerSpec extends Specification { then: 1 * controller.awsAutoScalingService.createLaunchConfigAndAutoScalingGroup(_, _, expectedLaunchConfiguration, - _, _) >> new CreateAutoScalingGroupResult() - 0 * controller.awsAutoScalingService.createLaunchConfigAndAutoScalingGroup(_, _, _, _, _) + _, _, _) >> new CreateAutoScalingGroupResult() + 0 * controller.awsAutoScalingService.createLaunchConfigAndAutoScalingGroup(_, _, _, _, _, _) where: ebsOptimizedParam | ebsOptimizedValue From 595b03ffb6b6f554a0576c9839ac3590dbe3eb05 Mon Sep 17 00:00:00 2001 From: Michael Rose Date: Tue, 24 Jun 2014 12:41:48 -0600 Subject: [PATCH 5/6] Passing through userMetaData to create method --- .../com/netflix/asgard/AutoScalingController.groovy | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/grails-app/controllers/com/netflix/asgard/AutoScalingController.groovy b/grails-app/controllers/com/netflix/asgard/AutoScalingController.groovy index a2b9b390..b1dee48d 100644 --- a/grails-app/controllers/com/netflix/asgard/AutoScalingController.groovy +++ b/grails-app/controllers/com/netflix/asgard/AutoScalingController.groovy @@ -251,6 +251,7 @@ class AutoScalingController { } Subnets subnets = awsEc2Service.getSubnets(userContext) Map purposeToVpcId = subnets.mapPurposeToVpcId() + Map userMetaData = params.userMetaData String subnetPurpose = params.subnetPurpose ?: null String vpcId = purposeToVpcId[subnetPurpose] Set appsWithClusterOptLevel = [] @@ -285,7 +286,8 @@ class AutoScalingController { iamInstanceProfile: configService.defaultIamRole, spotUrl: configService.spotUrl, isChaosMonkeyActive: cloudReadyService.isChaosMonkeyActive(userContext.region), - appsWithClusterOptLevel: appsWithClusterOptLevel ?: [] + appsWithClusterOptLevel: appsWithClusterOptLevel ?: [], + userMetaData: userMetaData ?: [:] ] } From 682b3607ab4079ac3b6f4130019e027e09bd8c28 Mon Sep 17 00:00:00 2001 From: Michael Rose Date: Mon, 30 Jun 2014 12:30:31 -0600 Subject: [PATCH 6/6] RollingPush supports userMetaData --- .../com/netflix/asgard/AutoScalingController.groovy | 4 ++-- .../controllers/com/netflix/asgard/ClusterController.groovy | 2 +- .../controllers/com/netflix/asgard/PushController.groovy | 3 ++- .../com/netflix/asgard/push/RollingPushOperation.groovy | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/grails-app/controllers/com/netflix/asgard/AutoScalingController.groovy b/grails-app/controllers/com/netflix/asgard/AutoScalingController.groovy index b1dee48d..d8f601bc 100644 --- a/grails-app/controllers/com/netflix/asgard/AutoScalingController.groovy +++ b/grails-app/controllers/com/netflix/asgard/AutoScalingController.groovy @@ -251,7 +251,7 @@ class AutoScalingController { } Subnets subnets = awsEc2Service.getSubnets(userContext) Map purposeToVpcId = subnets.mapPurposeToVpcId() - Map userMetaData = params.userMetaData + Map userMetaData = params.userMetaData ?: [:] String subnetPurpose = params.subnetPurpose ?: null String vpcId = purposeToVpcId[subnetPurpose] Set appsWithClusterOptLevel = [] @@ -357,7 +357,7 @@ class AutoScalingController { launchConfigTemplate.spotPrice = spotInstanceRequestService.recommendSpotPrice(userContext, instType) } boolean enableChaosMonkey = params.chaosMonkey == 'enabled' - Map userMetaData = params.userMetaData + Map userMetaData = params.userMetaData ?: [:] CreateAutoScalingGroupResult result = awsAutoScalingService.createLaunchConfigAndAutoScalingGroup( userContext, groupTemplate, launchConfigTemplate, suspendedProcesses, enableChaosMonkey, userMetaData) diff --git a/grails-app/controllers/com/netflix/asgard/ClusterController.groovy b/grails-app/controllers/com/netflix/asgard/ClusterController.groovy index 2eb4bdb7..60cc4d9c 100644 --- a/grails-app/controllers/com/netflix/asgard/ClusterController.groovy +++ b/grails-app/controllers/com/netflix/asgard/ClusterController.groovy @@ -267,7 +267,7 @@ Group: ${loadBalancerNames}""" groupName: nextGroupName, securityGroups: securityGroups, maxStartupRetries: convertToIntOrUseDefault(params.maxStartupRetries, 5), - userMetadata: params.userMetaData + userMetadata: params.userMetaData ?: [:] ), initialTraffic: initialTraffic, minSize: minSize, diff --git a/grails-app/controllers/com/netflix/asgard/PushController.groovy b/grails-app/controllers/com/netflix/asgard/PushController.groovy index 58088d1f..c5cea190 100644 --- a/grails-app/controllers/com/netflix/asgard/PushController.groovy +++ b/grails-app/controllers/com/netflix/asgard/PushController.groovy @@ -98,7 +98,8 @@ class PushController { instanceType: params.instanceType, groupName: groupName, securityGroups: selectedSecurityGroups, - maxStartupRetries: params.maxStartupRetries?.toInteger() ?: 5 + maxStartupRetries: params.maxStartupRetries?.toInteger() ?: 5, + userMetadata: params.userMetaData ?: [:] ), newestFirst: params.newestFirst == 'true', relaunchCount: relaunchCount, diff --git a/src/groovy/com/netflix/asgard/push/RollingPushOperation.groovy b/src/groovy/com/netflix/asgard/push/RollingPushOperation.groovy index 969ac8bd..6332df48 100644 --- a/src/groovy/com/netflix/asgard/push/RollingPushOperation.groovy +++ b/src/groovy/com/netflix/asgard/push/RollingPushOperation.groovy @@ -124,7 +124,7 @@ class RollingPushOperation extends AbstractPushOperation { AutoScalingGroupBeanOptions groupForUserData = AutoScalingGroupBeanOptions.from(group, subnets) groupForUserData.launchConfigurationName = newLaunchName launchConfig.userData = launchTemplateService.buildUserData(options.common.userContext, groupForUserData, - launchConfig) + launchConfig, options.common.userMetadata) awsAutoScalingService.createLaunchConfiguration(options.common.userContext, launchConfig, task) Time.sleepCancellably 200 // small pause before ASG update to avoid rate limiting