github.com/minamijoyo/terraform@v0.7.8-0.20161029001309-18b3736ba44b/builtin/providers/aws/resource_aws_elasticache_cluster.go (about)

     1  package aws
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"sort"
     7  	"strings"
     8  	"time"
     9  
    10  	"github.com/aws/aws-sdk-go/aws"
    11  	"github.com/aws/aws-sdk-go/aws/awserr"
    12  	"github.com/aws/aws-sdk-go/service/elasticache"
    13  	"github.com/hashicorp/terraform/helper/resource"
    14  	"github.com/hashicorp/terraform/helper/schema"
    15  )
    16  
    17  func resourceAwsElastiCacheCommonSchema() map[string]*schema.Schema {
    18  
    19  	return map[string]*schema.Schema{
    20  		"availability_zones": &schema.Schema{
    21  			Type:     schema.TypeSet,
    22  			Optional: true,
    23  			ForceNew: true,
    24  			Elem:     &schema.Schema{Type: schema.TypeString},
    25  			Set:      schema.HashString,
    26  		},
    27  		"node_type": &schema.Schema{
    28  			Type:     schema.TypeString,
    29  			Required: true,
    30  		},
    31  		"engine": &schema.Schema{
    32  			Type:     schema.TypeString,
    33  			Required: true,
    34  		},
    35  		"engine_version": &schema.Schema{
    36  			Type:     schema.TypeString,
    37  			Optional: true,
    38  			Computed: true,
    39  		},
    40  		"parameter_group_name": &schema.Schema{
    41  			Type:     schema.TypeString,
    42  			Optional: true,
    43  			Computed: true,
    44  		},
    45  		"subnet_group_name": &schema.Schema{
    46  			Type:     schema.TypeString,
    47  			Optional: true,
    48  			Computed: true,
    49  			ForceNew: true,
    50  		},
    51  		"security_group_names": &schema.Schema{
    52  			Type:     schema.TypeSet,
    53  			Optional: true,
    54  			Computed: true,
    55  			ForceNew: true,
    56  			Elem:     &schema.Schema{Type: schema.TypeString},
    57  			Set:      schema.HashString,
    58  		},
    59  		"security_group_ids": &schema.Schema{
    60  			Type:     schema.TypeSet,
    61  			Optional: true,
    62  			Computed: true,
    63  			Elem:     &schema.Schema{Type: schema.TypeString},
    64  			Set:      schema.HashString,
    65  		},
    66  		// A single-element string list containing an Amazon Resource Name (ARN) that
    67  		// uniquely identifies a Redis RDB snapshot file stored in Amazon S3. The snapshot
    68  		// file will be used to populate the node group.
    69  		//
    70  		// See also:
    71  		// https://github.com/aws/aws-sdk-go/blob/4862a174f7fc92fb523fc39e68f00b87d91d2c3d/service/elasticache/api.go#L2079
    72  		"snapshot_arns": &schema.Schema{
    73  			Type:     schema.TypeSet,
    74  			Optional: true,
    75  			ForceNew: true,
    76  			Elem:     &schema.Schema{Type: schema.TypeString},
    77  			Set:      schema.HashString,
    78  		},
    79  		"snapshot_window": &schema.Schema{
    80  			Type:     schema.TypeString,
    81  			Optional: true,
    82  			Computed: true,
    83  		},
    84  		"snapshot_name": &schema.Schema{
    85  			Type:     schema.TypeString,
    86  			Optional: true,
    87  			ForceNew: true,
    88  		},
    89  
    90  		"maintenance_window": &schema.Schema{
    91  			Type:     schema.TypeString,
    92  			Optional: true,
    93  			Computed: true,
    94  			StateFunc: func(val interface{}) string {
    95  				// Elasticache always changes the maintenance
    96  				// to lowercase
    97  				return strings.ToLower(val.(string))
    98  			},
    99  		},
   100  		"port": &schema.Schema{
   101  			Type:     schema.TypeInt,
   102  			Required: true,
   103  			ForceNew: true,
   104  		},
   105  		"notification_topic_arn": &schema.Schema{
   106  			Type:     schema.TypeString,
   107  			Optional: true,
   108  		},
   109  
   110  		"snapshot_retention_limit": &schema.Schema{
   111  			Type:     schema.TypeInt,
   112  			Optional: true,
   113  			ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
   114  				value := v.(int)
   115  				if value > 35 {
   116  					es = append(es, fmt.Errorf(
   117  						"snapshot retention limit cannot be more than 35 days"))
   118  				}
   119  				return
   120  			},
   121  		},
   122  
   123  		"apply_immediately": &schema.Schema{
   124  			Type:     schema.TypeBool,
   125  			Optional: true,
   126  			Computed: true,
   127  		},
   128  
   129  		"tags": tagsSchema(),
   130  	}
   131  }
   132  
   133  func resourceAwsElasticacheCluster() *schema.Resource {
   134  	resourceSchema := resourceAwsElastiCacheCommonSchema()
   135  
   136  	resourceSchema["cluster_id"] = &schema.Schema{
   137  		Type:     schema.TypeString,
   138  		Required: true,
   139  		ForceNew: true,
   140  		StateFunc: func(val interface{}) string {
   141  			// Elasticache normalizes cluster ids to lowercase,
   142  			// so we have to do this too or else we can end up
   143  			// with non-converging diffs.
   144  			return strings.ToLower(val.(string))
   145  		},
   146  		ValidateFunc: validateElastiCacheClusterId,
   147  	}
   148  
   149  	resourceSchema["num_cache_nodes"] = &schema.Schema{
   150  		Type:     schema.TypeInt,
   151  		Required: true,
   152  	}
   153  
   154  	resourceSchema["az_mode"] = &schema.Schema{
   155  		Type:     schema.TypeString,
   156  		Optional: true,
   157  		Computed: true,
   158  		ForceNew: true,
   159  	}
   160  
   161  	resourceSchema["availability_zone"] = &schema.Schema{
   162  		Type:     schema.TypeString,
   163  		Optional: true,
   164  		Computed: true,
   165  		ForceNew: true,
   166  	}
   167  
   168  	resourceSchema["configuration_endpoint"] = &schema.Schema{
   169  		Type:     schema.TypeString,
   170  		Computed: true,
   171  	}
   172  
   173  	resourceSchema["cluster_address"] = &schema.Schema{
   174  		Type:     schema.TypeString,
   175  		Computed: true,
   176  	}
   177  
   178  	resourceSchema["replication_group_id"] = &schema.Schema{
   179  		Type:     schema.TypeString,
   180  		Computed: true,
   181  	}
   182  
   183  	resourceSchema["cache_nodes"] = &schema.Schema{
   184  		Type:     schema.TypeList,
   185  		Computed: true,
   186  		Elem: &schema.Resource{
   187  			Schema: map[string]*schema.Schema{
   188  				"id": &schema.Schema{
   189  					Type:     schema.TypeString,
   190  					Computed: true,
   191  				},
   192  				"address": &schema.Schema{
   193  					Type:     schema.TypeString,
   194  					Computed: true,
   195  				},
   196  				"port": &schema.Schema{
   197  					Type:     schema.TypeInt,
   198  					Computed: true,
   199  				},
   200  				"availability_zone": &schema.Schema{
   201  					Type:     schema.TypeString,
   202  					Computed: true,
   203  				},
   204  			},
   205  		},
   206  	}
   207  
   208  	return &schema.Resource{
   209  		Create: resourceAwsElasticacheClusterCreate,
   210  		Read:   resourceAwsElasticacheClusterRead,
   211  		Update: resourceAwsElasticacheClusterUpdate,
   212  		Delete: resourceAwsElasticacheClusterDelete,
   213  		Importer: &schema.ResourceImporter{
   214  			State: schema.ImportStatePassthrough,
   215  		},
   216  
   217  		Schema: resourceSchema,
   218  	}
   219  }
   220  
   221  func resourceAwsElasticacheClusterCreate(d *schema.ResourceData, meta interface{}) error {
   222  	conn := meta.(*AWSClient).elasticacheconn
   223  
   224  	clusterId := d.Get("cluster_id").(string)
   225  	nodeType := d.Get("node_type").(string)           // e.g) cache.m1.small
   226  	numNodes := int64(d.Get("num_cache_nodes").(int)) // 2
   227  	engine := d.Get("engine").(string)                // memcached
   228  	engineVersion := d.Get("engine_version").(string) // 1.4.14
   229  	port := int64(d.Get("port").(int))                // e.g) 11211
   230  	subnetGroupName := d.Get("subnet_group_name").(string)
   231  	securityNameSet := d.Get("security_group_names").(*schema.Set)
   232  	securityIdSet := d.Get("security_group_ids").(*schema.Set)
   233  
   234  	securityNames := expandStringList(securityNameSet.List())
   235  	securityIds := expandStringList(securityIdSet.List())
   236  	tags := tagsFromMapEC(d.Get("tags").(map[string]interface{}))
   237  
   238  	req := &elasticache.CreateCacheClusterInput{
   239  		CacheClusterId:          aws.String(clusterId),
   240  		CacheNodeType:           aws.String(nodeType),
   241  		NumCacheNodes:           aws.Int64(numNodes),
   242  		Engine:                  aws.String(engine),
   243  		EngineVersion:           aws.String(engineVersion),
   244  		Port:                    aws.Int64(port),
   245  		CacheSubnetGroupName:    aws.String(subnetGroupName),
   246  		CacheSecurityGroupNames: securityNames,
   247  		SecurityGroupIds:        securityIds,
   248  		Tags:                    tags,
   249  	}
   250  
   251  	// parameter groups are optional and can be defaulted by AWS
   252  	if v, ok := d.GetOk("parameter_group_name"); ok {
   253  		req.CacheParameterGroupName = aws.String(v.(string))
   254  	}
   255  
   256  	if v, ok := d.GetOk("snapshot_retention_limit"); ok {
   257  		req.SnapshotRetentionLimit = aws.Int64(int64(v.(int)))
   258  	}
   259  
   260  	if v, ok := d.GetOk("snapshot_window"); ok {
   261  		req.SnapshotWindow = aws.String(v.(string))
   262  	}
   263  
   264  	if v, ok := d.GetOk("maintenance_window"); ok {
   265  		req.PreferredMaintenanceWindow = aws.String(v.(string))
   266  	}
   267  
   268  	if v, ok := d.GetOk("notification_topic_arn"); ok {
   269  		req.NotificationTopicArn = aws.String(v.(string))
   270  	}
   271  
   272  	snaps := d.Get("snapshot_arns").(*schema.Set).List()
   273  	if len(snaps) > 0 {
   274  		s := expandStringList(snaps)
   275  		req.SnapshotArns = s
   276  		log.Printf("[DEBUG] Restoring Redis cluster from S3 snapshot: %#v", s)
   277  	}
   278  
   279  	if v, ok := d.GetOk("snapshot_name"); ok {
   280  		req.SnapshotName = aws.String(v.(string))
   281  	}
   282  
   283  	if v, ok := d.GetOk("az_mode"); ok {
   284  		req.AZMode = aws.String(v.(string))
   285  	}
   286  
   287  	if v, ok := d.GetOk("availability_zone"); ok {
   288  		req.PreferredAvailabilityZone = aws.String(v.(string))
   289  	}
   290  
   291  	preferred_azs := d.Get("availability_zones").(*schema.Set).List()
   292  	if len(preferred_azs) > 0 {
   293  		azs := expandStringList(preferred_azs)
   294  		req.PreferredAvailabilityZones = azs
   295  	}
   296  
   297  	if v, ok := d.GetOk("replication_group_id"); ok {
   298  		req.ReplicationGroupId = aws.String(v.(string))
   299  	}
   300  
   301  	resp, err := conn.CreateCacheCluster(req)
   302  	if err != nil {
   303  		return fmt.Errorf("Error creating Elasticache: %s", err)
   304  	}
   305  
   306  	// Assign the cluster id as the resource ID
   307  	// Elasticache always retains the id in lower case, so we have to
   308  	// mimic that or else we won't be able to refresh a resource whose
   309  	// name contained uppercase characters.
   310  	d.SetId(strings.ToLower(*resp.CacheCluster.CacheClusterId))
   311  
   312  	pending := []string{"creating", "modifying", "restoring"}
   313  	stateConf := &resource.StateChangeConf{
   314  		Pending:    pending,
   315  		Target:     []string{"available"},
   316  		Refresh:    cacheClusterStateRefreshFunc(conn, d.Id(), "available", pending),
   317  		Timeout:    40 * time.Minute,
   318  		MinTimeout: 10 * time.Second,
   319  		Delay:      30 * time.Second,
   320  	}
   321  
   322  	log.Printf("[DEBUG] Waiting for state to become available: %v", d.Id())
   323  	_, sterr := stateConf.WaitForState()
   324  	if sterr != nil {
   325  		return fmt.Errorf("Error waiting for elasticache (%s) to be created: %s", d.Id(), sterr)
   326  	}
   327  
   328  	return resourceAwsElasticacheClusterRead(d, meta)
   329  }
   330  
   331  func resourceAwsElasticacheClusterRead(d *schema.ResourceData, meta interface{}) error {
   332  	conn := meta.(*AWSClient).elasticacheconn
   333  	req := &elasticache.DescribeCacheClustersInput{
   334  		CacheClusterId:    aws.String(d.Id()),
   335  		ShowCacheNodeInfo: aws.Bool(true),
   336  	}
   337  
   338  	res, err := conn.DescribeCacheClusters(req)
   339  	if err != nil {
   340  		if eccErr, ok := err.(awserr.Error); ok && eccErr.Code() == "CacheClusterNotFound" {
   341  			log.Printf("[WARN] ElastiCache Cluster (%s) not found", d.Id())
   342  			d.SetId("")
   343  			return nil
   344  		}
   345  
   346  		return err
   347  	}
   348  
   349  	if len(res.CacheClusters) == 1 {
   350  		c := res.CacheClusters[0]
   351  		d.Set("cluster_id", c.CacheClusterId)
   352  		d.Set("node_type", c.CacheNodeType)
   353  		d.Set("num_cache_nodes", c.NumCacheNodes)
   354  		d.Set("engine", c.Engine)
   355  		d.Set("engine_version", c.EngineVersion)
   356  		if c.ConfigurationEndpoint != nil {
   357  			d.Set("port", c.ConfigurationEndpoint.Port)
   358  			d.Set("configuration_endpoint", aws.String(fmt.Sprintf("%s:%d", *c.ConfigurationEndpoint.Address, *c.ConfigurationEndpoint.Port)))
   359  			d.Set("cluster_address", aws.String(fmt.Sprintf("%s", *c.ConfigurationEndpoint.Address)))
   360  		}
   361  
   362  		if c.ReplicationGroupId != nil {
   363  			d.Set("replication_group_id", c.ReplicationGroupId)
   364  		}
   365  
   366  		d.Set("subnet_group_name", c.CacheSubnetGroupName)
   367  		d.Set("security_group_names", flattenElastiCacheSecurityGroupNames(c.CacheSecurityGroups))
   368  		d.Set("security_group_ids", flattenElastiCacheSecurityGroupIds(c.SecurityGroups))
   369  		if c.CacheParameterGroup != nil {
   370  			d.Set("parameter_group_name", c.CacheParameterGroup.CacheParameterGroupName)
   371  		}
   372  		d.Set("maintenance_window", c.PreferredMaintenanceWindow)
   373  		d.Set("snapshot_window", c.SnapshotWindow)
   374  		d.Set("snapshot_retention_limit", c.SnapshotRetentionLimit)
   375  		if c.NotificationConfiguration != nil {
   376  			if *c.NotificationConfiguration.TopicStatus == "active" {
   377  				d.Set("notification_topic_arn", c.NotificationConfiguration.TopicArn)
   378  			}
   379  		}
   380  		d.Set("availability_zone", c.PreferredAvailabilityZone)
   381  
   382  		if err := setCacheNodeData(d, c); err != nil {
   383  			return err
   384  		}
   385  		// list tags for resource
   386  		// set tags
   387  		arn, err := buildECARN(d.Id(), meta.(*AWSClient).partition, meta.(*AWSClient).accountid, meta.(*AWSClient).region)
   388  		if err != nil {
   389  			log.Printf("[DEBUG] Error building ARN for ElastiCache Cluster, not setting Tags for cluster %s", *c.CacheClusterId)
   390  		} else {
   391  			resp, err := conn.ListTagsForResource(&elasticache.ListTagsForResourceInput{
   392  				ResourceName: aws.String(arn),
   393  			})
   394  
   395  			if err != nil {
   396  				log.Printf("[DEBUG] Error retrieving tags for ARN: %s", arn)
   397  			}
   398  
   399  			var et []*elasticache.Tag
   400  			if len(resp.TagList) > 0 {
   401  				et = resp.TagList
   402  			}
   403  			d.Set("tags", tagsToMapEC(et))
   404  		}
   405  	}
   406  
   407  	return nil
   408  }
   409  
   410  func resourceAwsElasticacheClusterUpdate(d *schema.ResourceData, meta interface{}) error {
   411  	conn := meta.(*AWSClient).elasticacheconn
   412  	arn, err := buildECARN(d.Id(), meta.(*AWSClient).partition, meta.(*AWSClient).accountid, meta.(*AWSClient).region)
   413  	if err != nil {
   414  		log.Printf("[DEBUG] Error building ARN for ElastiCache Cluster, not updating Tags for cluster %s", d.Id())
   415  	} else {
   416  		if err := setTagsEC(conn, d, arn); err != nil {
   417  			return err
   418  		}
   419  	}
   420  
   421  	req := &elasticache.ModifyCacheClusterInput{
   422  		CacheClusterId:   aws.String(d.Id()),
   423  		ApplyImmediately: aws.Bool(d.Get("apply_immediately").(bool)),
   424  	}
   425  
   426  	requestUpdate := false
   427  	if d.HasChange("security_group_ids") {
   428  		if attr := d.Get("security_group_ids").(*schema.Set); attr.Len() > 0 {
   429  			req.SecurityGroupIds = expandStringList(attr.List())
   430  			requestUpdate = true
   431  		}
   432  	}
   433  
   434  	if d.HasChange("parameter_group_name") {
   435  		req.CacheParameterGroupName = aws.String(d.Get("parameter_group_name").(string))
   436  		requestUpdate = true
   437  	}
   438  
   439  	if d.HasChange("maintenance_window") {
   440  		req.PreferredMaintenanceWindow = aws.String(d.Get("maintenance_window").(string))
   441  		requestUpdate = true
   442  	}
   443  
   444  	if d.HasChange("notification_topic_arn") {
   445  		v := d.Get("notification_topic_arn").(string)
   446  		req.NotificationTopicArn = aws.String(v)
   447  		if v == "" {
   448  			inactive := "inactive"
   449  			req.NotificationTopicStatus = &inactive
   450  		}
   451  		requestUpdate = true
   452  	}
   453  
   454  	if d.HasChange("engine_version") {
   455  		req.EngineVersion = aws.String(d.Get("engine_version").(string))
   456  		requestUpdate = true
   457  	}
   458  
   459  	if d.HasChange("snapshot_window") {
   460  		req.SnapshotWindow = aws.String(d.Get("snapshot_window").(string))
   461  		requestUpdate = true
   462  	}
   463  
   464  	if d.HasChange("node_type") {
   465  		req.CacheNodeType = aws.String(d.Get("node_type").(string))
   466  		requestUpdate = true
   467  	}
   468  
   469  	if d.HasChange("snapshot_retention_limit") {
   470  		req.SnapshotRetentionLimit = aws.Int64(int64(d.Get("snapshot_retention_limit").(int)))
   471  		requestUpdate = true
   472  	}
   473  
   474  	if d.HasChange("num_cache_nodes") {
   475  		oraw, nraw := d.GetChange("num_cache_nodes")
   476  		o := oraw.(int)
   477  		n := nraw.(int)
   478  		if v, ok := d.GetOk("az_mode"); ok && v.(string) == "cross-az" && n == 1 {
   479  			return fmt.Errorf("[WARN] Error updateing Elasticache cluster (%s), error: Cross-AZ mode is not supported in a single cache node.", d.Id())
   480  		}
   481  		if n < o {
   482  			log.Printf("[INFO] Cluster %s is marked for Decreasing cache nodes from %d to %d", d.Id(), o, n)
   483  			nodesToRemove := getCacheNodesToRemove(d, o, o-n)
   484  			req.CacheNodeIdsToRemove = nodesToRemove
   485  		}
   486  
   487  		req.NumCacheNodes = aws.Int64(int64(d.Get("num_cache_nodes").(int)))
   488  		requestUpdate = true
   489  
   490  	}
   491  
   492  	if requestUpdate {
   493  		log.Printf("[DEBUG] Modifying ElastiCache Cluster (%s), opts:\n%s", d.Id(), req)
   494  		_, err := conn.ModifyCacheCluster(req)
   495  		if err != nil {
   496  			return fmt.Errorf("[WARN] Error updating ElastiCache cluster (%s), error: %s", d.Id(), err)
   497  		}
   498  
   499  		log.Printf("[DEBUG] Waiting for update: %s", d.Id())
   500  		pending := []string{"modifying", "rebooting cache cluster nodes", "snapshotting"}
   501  		stateConf := &resource.StateChangeConf{
   502  			Pending:    pending,
   503  			Target:     []string{"available"},
   504  			Refresh:    cacheClusterStateRefreshFunc(conn, d.Id(), "available", pending),
   505  			Timeout:    80 * time.Minute,
   506  			MinTimeout: 10 * time.Second,
   507  			Delay:      30 * time.Second,
   508  		}
   509  
   510  		_, sterr := stateConf.WaitForState()
   511  		if sterr != nil {
   512  			return fmt.Errorf("Error waiting for elasticache (%s) to update: %s", d.Id(), sterr)
   513  		}
   514  	}
   515  
   516  	return resourceAwsElasticacheClusterRead(d, meta)
   517  }
   518  
   519  func getCacheNodesToRemove(d *schema.ResourceData, oldNumberOfNodes int, cacheNodesToRemove int) []*string {
   520  	nodesIdsToRemove := []*string{}
   521  	for i := oldNumberOfNodes; i > oldNumberOfNodes-cacheNodesToRemove && i > 0; i-- {
   522  		s := fmt.Sprintf("%04d", i)
   523  		nodesIdsToRemove = append(nodesIdsToRemove, &s)
   524  	}
   525  
   526  	return nodesIdsToRemove
   527  }
   528  
   529  func setCacheNodeData(d *schema.ResourceData, c *elasticache.CacheCluster) error {
   530  	sortedCacheNodes := make([]*elasticache.CacheNode, len(c.CacheNodes))
   531  	copy(sortedCacheNodes, c.CacheNodes)
   532  	sort.Sort(byCacheNodeId(sortedCacheNodes))
   533  
   534  	cacheNodeData := make([]map[string]interface{}, 0, len(sortedCacheNodes))
   535  
   536  	for _, node := range sortedCacheNodes {
   537  		if node.CacheNodeId == nil || node.Endpoint == nil || node.Endpoint.Address == nil || node.Endpoint.Port == nil || node.CustomerAvailabilityZone == nil {
   538  			return fmt.Errorf("Unexpected nil pointer in: %s", node)
   539  		}
   540  		cacheNodeData = append(cacheNodeData, map[string]interface{}{
   541  			"id":                *node.CacheNodeId,
   542  			"address":           *node.Endpoint.Address,
   543  			"port":              int(*node.Endpoint.Port),
   544  			"availability_zone": *node.CustomerAvailabilityZone,
   545  		})
   546  	}
   547  
   548  	return d.Set("cache_nodes", cacheNodeData)
   549  }
   550  
   551  type byCacheNodeId []*elasticache.CacheNode
   552  
   553  func (b byCacheNodeId) Len() int      { return len(b) }
   554  func (b byCacheNodeId) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
   555  func (b byCacheNodeId) Less(i, j int) bool {
   556  	return b[i].CacheNodeId != nil && b[j].CacheNodeId != nil &&
   557  		*b[i].CacheNodeId < *b[j].CacheNodeId
   558  }
   559  
   560  func resourceAwsElasticacheClusterDelete(d *schema.ResourceData, meta interface{}) error {
   561  	conn := meta.(*AWSClient).elasticacheconn
   562  
   563  	req := &elasticache.DeleteCacheClusterInput{
   564  		CacheClusterId: aws.String(d.Id()),
   565  	}
   566  	_, err := conn.DeleteCacheCluster(req)
   567  	if err != nil {
   568  		return err
   569  	}
   570  
   571  	log.Printf("[DEBUG] Waiting for deletion: %v", d.Id())
   572  	stateConf := &resource.StateChangeConf{
   573  		Pending:    []string{"creating", "available", "deleting", "incompatible-parameters", "incompatible-network", "restore-failed"},
   574  		Target:     []string{},
   575  		Refresh:    cacheClusterStateRefreshFunc(conn, d.Id(), "", []string{}),
   576  		Timeout:    40 * time.Minute,
   577  		MinTimeout: 10 * time.Second,
   578  		Delay:      30 * time.Second,
   579  	}
   580  
   581  	_, sterr := stateConf.WaitForState()
   582  	if sterr != nil {
   583  		return fmt.Errorf("Error waiting for elasticache (%s) to delete: %s", d.Id(), sterr)
   584  	}
   585  
   586  	d.SetId("")
   587  
   588  	return nil
   589  }
   590  
   591  func cacheClusterStateRefreshFunc(conn *elasticache.ElastiCache, clusterID, givenState string, pending []string) resource.StateRefreshFunc {
   592  	return func() (interface{}, string, error) {
   593  		resp, err := conn.DescribeCacheClusters(&elasticache.DescribeCacheClustersInput{
   594  			CacheClusterId:    aws.String(clusterID),
   595  			ShowCacheNodeInfo: aws.Bool(true),
   596  		})
   597  		if err != nil {
   598  			apierr := err.(awserr.Error)
   599  			log.Printf("[DEBUG] message: %v, code: %v", apierr.Message(), apierr.Code())
   600  			if apierr.Message() == fmt.Sprintf("CacheCluster not found: %v", clusterID) {
   601  				log.Printf("[DEBUG] Detect deletion")
   602  				return nil, "", nil
   603  			}
   604  
   605  			log.Printf("[ERROR] CacheClusterStateRefreshFunc: %s", err)
   606  			return nil, "", err
   607  		}
   608  
   609  		if len(resp.CacheClusters) == 0 {
   610  			return nil, "", fmt.Errorf("[WARN] Error: no Cache Clusters found for id (%s)", clusterID)
   611  		}
   612  
   613  		var c *elasticache.CacheCluster
   614  		for _, cluster := range resp.CacheClusters {
   615  			if *cluster.CacheClusterId == clusterID {
   616  				log.Printf("[DEBUG] Found matching ElastiCache cluster: %s", *cluster.CacheClusterId)
   617  				c = cluster
   618  			}
   619  		}
   620  
   621  		if c == nil {
   622  			return nil, "", fmt.Errorf("[WARN] Error: no matching Elastic Cache cluster for id (%s)", clusterID)
   623  		}
   624  
   625  		log.Printf("[DEBUG] ElastiCache Cluster (%s) status: %v", clusterID, *c.CacheClusterStatus)
   626  
   627  		// return the current state if it's in the pending array
   628  		for _, p := range pending {
   629  			log.Printf("[DEBUG] ElastiCache: checking pending state (%s) for cluster (%s), cluster status: %s", pending, clusterID, *c.CacheClusterStatus)
   630  			s := *c.CacheClusterStatus
   631  			if p == s {
   632  				log.Printf("[DEBUG] Return with status: %v", *c.CacheClusterStatus)
   633  				return c, p, nil
   634  			}
   635  		}
   636  
   637  		// return given state if it's not in pending
   638  		if givenState != "" {
   639  			log.Printf("[DEBUG] ElastiCache: checking given state (%s) of cluster (%s) against cluster status (%s)", givenState, clusterID, *c.CacheClusterStatus)
   640  			// check to make sure we have the node count we're expecting
   641  			if int64(len(c.CacheNodes)) != *c.NumCacheNodes {
   642  				log.Printf("[DEBUG] Node count is not what is expected: %d found, %d expected", len(c.CacheNodes), *c.NumCacheNodes)
   643  				return nil, "creating", nil
   644  			}
   645  
   646  			log.Printf("[DEBUG] Node count matched (%d)", len(c.CacheNodes))
   647  			// loop the nodes and check their status as well
   648  			for _, n := range c.CacheNodes {
   649  				log.Printf("[DEBUG] Checking cache node for status: %s", n)
   650  				if n.CacheNodeStatus != nil && *n.CacheNodeStatus != "available" {
   651  					log.Printf("[DEBUG] Node (%s) is not yet available, status: %s", *n.CacheNodeId, *n.CacheNodeStatus)
   652  					return nil, "creating", nil
   653  				}
   654  				log.Printf("[DEBUG] Cache node not in expected state")
   655  			}
   656  			log.Printf("[DEBUG] ElastiCache returning given state (%s), cluster: %s", givenState, c)
   657  			return c, givenState, nil
   658  		}
   659  		log.Printf("[DEBUG] current status: %v", *c.CacheClusterStatus)
   660  		return c, *c.CacheClusterStatus, nil
   661  	}
   662  }
   663  
   664  func buildECARN(identifier, partition, accountid, region string) (string, error) {
   665  	if partition == "" {
   666  		return "", fmt.Errorf("Unable to construct ElastiCache ARN because of missing AWS partition")
   667  	}
   668  	if accountid == "" {
   669  		return "", fmt.Errorf("Unable to construct ElastiCache ARN because of missing AWS Account ID")
   670  	}
   671  	arn := fmt.Sprintf("arn:%s:elasticache:%s:%s:cluster:%s", partition, region, accountid, identifier)
   672  	return arn, nil
   673  
   674  }