Example #1
0
def deploy(ami_id):
    """
    Deploys an AMI as an auto-scaling group (ASG) to AWS.

    Arguments:
        ami_id(str): AWS AMI ID

    Returns:
        dict(str, str, dict): Returns a dictionary with the keys:
            'ami_id' - AMI id used to deploy the AMI
            'current_asgs' - Lists of current active ASGs, keyed by cluster.
            'disabled_asgs' - Lists of current inactive ASGs, keyed by cluster.

    Raises:
        TimeoutException: When the task to bring up the new instance times out.
        BackendError: When the task to bring up the new instance fails.
        ASGDoesNotExistException: If the ASG being queried does not exist.
    """
    LOG.info("Processing request to deploy {}.".format(ami_id))

    # Pull the EDP from the AMI ID
    edp = ec2.edp_for_ami(ami_id)

    # These are all autoscaling groups that match the tags we care about.
    existing_edp_asgs = ec2.asgs_for_edp(edp, filter_asgs_pending_delete=False)

    # Find the clusters for all the existing ASGs.
    existing_clustered_asgs = clusters_for_asgs(existing_edp_asgs)
    LOG.info("Deploying to cluster(s) {}".format(existing_clustered_asgs.keys()))

    # Create a new ASG in each cluster.
    new_clustered_asgs = defaultdict(list)
    for cluster in existing_clustered_asgs:
        try:
            newest_asg = new_asg(cluster, ami_id)
            new_clustered_asgs[cluster].append(newest_asg)
        except:
            msg = "ASG creation failed for cluster '{}' but succeeded for cluster(s) {}."
            msg = msg.format(cluster, new_clustered_asgs.keys())
            LOG.error(msg)
            raise

    new_asgs = [asgs[0] for asgs in new_clustered_asgs.values()]
    LOG.info("New ASGs created: {}".format(new_asgs))
    ec2.wait_for_in_service(new_asgs, 300)
    LOG.info("New ASGs healthy: {}".format(new_asgs))

    LOG.info("Enabling traffic to new ASGs for the {} cluster(s).".format(existing_clustered_asgs.keys()))
    success, enabled_asgs, disabled_asgs = _red_black_deploy(dict(new_clustered_asgs), existing_clustered_asgs)
    if not success:
        raise BackendError("Error performing red/black deploy - deploy was unsuccessful. "
                           "enabled_asgs: {} - disabled_asgs: {}".format(enabled_asgs, disabled_asgs))

    LOG.info("Woot! Deploy Done!")
    return {'ami_id': ami_id, 'current_asgs': enabled_asgs, 'disabled_asgs': disabled_asgs}
Example #2
0
def deploy(ami_id):
    """
    Deploys an AMI as an auto-scaling group (ASG) to AWS.

    Arguments:
        ami_id(str): AWS AMI ID

    Returns:
        dict(str, str, dict): Returns a dictionary with the keys:
            'ami_id' - AMI id used to deploy the AMI
            'current_asgs' - Lists of current active ASGs, keyed by cluster.
            'disabled_asgs' - Lists of current inactive ASGs, keyed by cluster.

    Raises:
        TimeoutException: When the task to bring up the new instance times out.
        BackendError: When the task to bring up the new instance fails.
        ASGDoesNotExistException: If the ASG being queried does not exist.
    """
    LOG.info("Processing request to deploy {}.".format(ami_id))

    # Pull the EDP from the AMI ID
    edp = ec2.edp_for_ami(ami_id)

    # These are all autoscaling groups that match the tags we care about.
    existing_edp_asgs = ec2.asgs_for_edp(edp, filter_asgs_pending_delete=False)

    # Find the clusters for all the existing ASGs.
    existing_clustered_asgs = clusters_for_asgs(existing_edp_asgs)
    LOG.info("Deploying to cluster(s) {}".format(existing_clustered_asgs.keys()))

    # Create a new ASG in each cluster.
    new_clustered_asgs = defaultdict(list)
    for cluster in existing_clustered_asgs:
        try:
            newest_asg = new_asg(cluster, ami_id)
            new_clustered_asgs[cluster].append(newest_asg)
        except:
            msg = "ASG creation failed for cluster '{}' but succeeded for cluster(s) {}."
            msg = msg.format(cluster, new_clustered_asgs.keys())
            LOG.exception(msg)
            raise

    new_asgs = [asgs[0] for asgs in new_clustered_asgs.values()]
    LOG.info("New ASGs created: {}".format(new_asgs))
    ec2.wait_for_in_service(new_asgs, 300)
    LOG.info("New ASGs healthy: {}".format(new_asgs))

    LOG.info("Enabling traffic to new ASGs for the {} cluster(s).".format(existing_clustered_asgs.keys()))
    success, enabled_asgs, disabled_asgs = _red_black_deploy(dict(new_clustered_asgs), existing_clustered_asgs)
    if not success:
        raise BackendError("Error performing red/black deploy - deploy was unsuccessful. "
                           "enabled_asgs: {} - disabled_asgs: {}".format(enabled_asgs, disabled_asgs))

    LOG.info("Woot! Deploy Done!")
    return {'ami_id': ami_id, 'current_asgs': enabled_asgs, 'disabled_asgs': disabled_asgs}
Example #3
0
def rollback(current_clustered_asgs, rollback_to_clustered_asgs, ami_id=None):
    """
    Rollback to a particular list of ASGs for one or more clusters.
    If rollback does not succeed, create new ASGs based on the AMI ID and deploy those ASGs.

    Arguments:
        current_clustered_asgs(dict): ASGs currently enabled, grouped by cluster.
        rollback_to_clustered_asgs(dict): ASGs to rollback to, grouped by cluster.
        ami_id(str): AWS AMI ID to which to rollback to.

    Returns:
        dict(str, str, dict): Returns a dictionary with the keys:
            'ami_id' - AMI id used to deploy the AMI, None if unspecified.
            'current_ami_id' -  The AMI that is running in an environment after a rollback.
            'current_asgs' - Lists of current active ASGs, keyed by cluster.
            'disabled_ami_id' - The AMI that was running in an environment before the rollback.
            'disabled_asgs' - Lists of current inactive ASGs, keyed by cluster.

    Raises:
        TimeoutException: When the task to bring up the new instance times out.
        BackendError: When the task to bring up the new instance fails.
        ASGDoesNotExistException: If the ASG being queried does not exist.
    """
    # First, ensure that the ASGs to which we'll rollback are not tagged for deletion.
    # Also, ensure that those same ASGs are not in the process of deletion.
    rollback_ready = True
    asgs_tagged_for_deletion = [asg.name for asg in ec2.get_asgs_pending_delete()]
    for asgs in rollback_to_clustered_asgs.values():
        for asg in asgs:
            try:
                if asg in asgs_tagged_for_deletion:
                    # ASG is tagged for deletion. Remove the deletion tag.
                    ec2.remove_asg_deletion_tag(asg)
                if is_asg_pending_delete(asg):
                    # Too late for rollback - this ASG is already pending delete.
                    LOG.info("Rollback ASG '{}' is pending delete. Aborting rollback to ASGs.".format(asg))
                    rollback_ready = False
                    break
            except ASGDoesNotExistException:
                LOG.info("Rollback ASG '{}' has been removed. Aborting rollback to ASGs.".format(asg))
                rollback_ready = False
                break
    disabled_ami_id = None
    try:
        # fetch the currently deployed AMI_ID for the clusters for logging
        if ami_id:
            edp = ec2.edp_for_ami(ami_id)
            disabled_ami_id = ec2.active_ami_for_edp(edp.environment, edp.deployment, edp.play)
    except (MissingTagException, ImageNotFoundException, MultipleImagesFoundException):
        # don't want to fail on this info not being available, we'll assign UNKNOWN and
        # continue with the rollback
        disabled_ami_id = 'UNKNOWN'

    if rollback_ready:
        # Perform the rollback.
        success, enabled_asgs, disabled_asgs = _red_black_deploy(rollback_to_clustered_asgs, current_clustered_asgs)
        if not success:
            LOG.info("Rollback failed for cluster(s) {}.".format(current_clustered_asgs.keys()))
        else:
            LOG.info("Woot! Rollback Done!")
            return {
                'ami_id': ami_id,
                'current_ami_id': ami_id,
                'current_asgs': enabled_asgs,
                'disabled_ami_id': disabled_ami_id,
                'disabled_asgs': disabled_asgs
            }

    # Rollback failed -or- wasn't attempted. Attempt a deploy.
    if ami_id:
        LOG.info("Attempting rollback via deploy of AMI {}.".format(ami_id))
        return deploy(ami_id)
    else:
        LOG.info("No AMI id specified - so no deploy occurred during rollback.")
        return {
            'ami_id': None,
            'current_ami_id': ami_id,
            'current_asgs': current_clustered_asgs,
            'disabled_ami_id': disabled_ami_id,
            'disabled_asgs': rollback_to_clustered_asgs
        }
Example #4
0
    def test_edp2_for_tagged_ami(self):
        actual_edp = ec2.edp_for_ami(self._make_fake_ami())
        expected_edp = EDP("foo", "bar", "baz")

        # Happy Path
        self.assertEqual(expected_edp, actual_edp)
Example #5
0
    def test_edp2_for_tagged_ami(self):
        actual_edp = ec2.edp_for_ami(self._make_fake_ami())
        expected_edp = EDP("foo", "bar", "baz")

        # Happy Path
        self.assertEqual(expected_edp, actual_edp)