github.com/minamijoyo/terraform@v0.7.8-0.20161029001309-18b3736ba44b/builtin/providers/openstack/resource_openstack_blockstorage_volume_v2.go (about) 1 package openstack 2 3 import ( 4 "bytes" 5 "fmt" 6 "log" 7 "time" 8 9 "github.com/gophercloud/gophercloud" 10 "github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes" 11 "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach" 12 "github.com/hashicorp/terraform/helper/hashcode" 13 "github.com/hashicorp/terraform/helper/resource" 14 "github.com/hashicorp/terraform/helper/schema" 15 ) 16 17 func resourceBlockStorageVolumeV2() *schema.Resource { 18 return &schema.Resource{ 19 Create: resourceBlockStorageVolumeV2Create, 20 Read: resourceBlockStorageVolumeV2Read, 21 Update: resourceBlockStorageVolumeV2Update, 22 Delete: resourceBlockStorageVolumeV2Delete, 23 Importer: &schema.ResourceImporter{ 24 State: schema.ImportStatePassthrough, 25 }, 26 27 Schema: map[string]*schema.Schema{ 28 "region": &schema.Schema{ 29 Type: schema.TypeString, 30 Required: true, 31 ForceNew: true, 32 DefaultFunc: schema.EnvDefaultFunc("OS_REGION_NAME", ""), 33 }, 34 "size": &schema.Schema{ 35 Type: schema.TypeInt, 36 Required: true, 37 ForceNew: true, 38 }, 39 "name": &schema.Schema{ 40 Type: schema.TypeString, 41 Optional: true, 42 ForceNew: false, 43 }, 44 "description": &schema.Schema{ 45 Type: schema.TypeString, 46 Optional: true, 47 ForceNew: false, 48 }, 49 "availability_zone": &schema.Schema{ 50 Type: schema.TypeString, 51 Optional: true, 52 ForceNew: true, 53 Computed: true, 54 }, 55 "metadata": &schema.Schema{ 56 Type: schema.TypeMap, 57 Optional: true, 58 ForceNew: false, 59 Computed: true, 60 }, 61 "snapshot_id": &schema.Schema{ 62 Type: schema.TypeString, 63 Optional: true, 64 ForceNew: true, 65 }, 66 "source_vol_id": &schema.Schema{ 67 Type: schema.TypeString, 68 Optional: true, 69 ForceNew: true, 70 }, 71 "image_id": &schema.Schema{ 72 Type: schema.TypeString, 73 Optional: true, 74 ForceNew: true, 75 }, 76 "volume_type": &schema.Schema{ 77 Type: schema.TypeString, 78 Optional: true, 79 ForceNew: true, 80 Computed: true, 81 }, 82 "consistency_group_id": &schema.Schema{ 83 Type: schema.TypeString, 84 Optional: true, 85 ForceNew: true, 86 }, 87 "source_replica": &schema.Schema{ 88 Type: schema.TypeString, 89 Optional: true, 90 ForceNew: true, 91 }, 92 "attachment": &schema.Schema{ 93 Type: schema.TypeSet, 94 Computed: true, 95 Elem: &schema.Resource{ 96 Schema: map[string]*schema.Schema{ 97 "id": &schema.Schema{ 98 Type: schema.TypeString, 99 Computed: true, 100 }, 101 "instance_id": &schema.Schema{ 102 Type: schema.TypeString, 103 Computed: true, 104 }, 105 "device": &schema.Schema{ 106 Type: schema.TypeString, 107 Computed: true, 108 }, 109 }, 110 }, 111 Set: resourceVolumeV2AttachmentHash, 112 }, 113 }, 114 } 115 } 116 117 func resourceBlockStorageVolumeV2Create(d *schema.ResourceData, meta interface{}) error { 118 config := meta.(*Config) 119 blockStorageClient, err := config.blockStorageV2Client(d.Get("region").(string)) 120 if err != nil { 121 return fmt.Errorf("Error creating OpenStack block storage client: %s", err) 122 } 123 124 createOpts := &volumes.CreateOpts{ 125 AvailabilityZone: d.Get("availability_zone").(string), 126 ConsistencyGroupID: d.Get("consistency_group_id").(string), 127 Description: d.Get("description").(string), 128 ImageID: d.Get("image_id").(string), 129 Metadata: resourceContainerMetadataV2(d), 130 Name: d.Get("name").(string), 131 Size: d.Get("size").(int), 132 SnapshotID: d.Get("snapshot_id").(string), 133 SourceReplica: d.Get("source_replica").(string), 134 SourceVolID: d.Get("source_vol_id").(string), 135 VolumeType: d.Get("volume_type").(string), 136 } 137 138 log.Printf("[DEBUG] Create Options: %#v", createOpts) 139 v, err := volumes.Create(blockStorageClient, createOpts).Extract() 140 if err != nil { 141 return fmt.Errorf("Error creating OpenStack volume: %s", err) 142 } 143 log.Printf("[INFO] Volume ID: %s", v.ID) 144 145 // Store the ID now 146 d.SetId(v.ID) 147 148 // Wait for the volume to become available. 149 log.Printf( 150 "[DEBUG] Waiting for volume (%s) to become available", 151 v.ID) 152 153 stateConf := &resource.StateChangeConf{ 154 Pending: []string{"downloading", "creating"}, 155 Target: []string{"available"}, 156 Refresh: VolumeV2StateRefreshFunc(blockStorageClient, v.ID), 157 Timeout: 10 * time.Minute, 158 Delay: 10 * time.Second, 159 MinTimeout: 3 * time.Second, 160 } 161 162 _, err = stateConf.WaitForState() 163 if err != nil { 164 return fmt.Errorf( 165 "Error waiting for volume (%s) to become ready: %s", 166 v.ID, err) 167 } 168 169 return resourceBlockStorageVolumeV2Read(d, meta) 170 } 171 172 func resourceBlockStorageVolumeV2Read(d *schema.ResourceData, meta interface{}) error { 173 config := meta.(*Config) 174 blockStorageClient, err := config.blockStorageV2Client(d.Get("region").(string)) 175 if err != nil { 176 return fmt.Errorf("Error creating OpenStack block storage client: %s", err) 177 } 178 179 v, err := volumes.Get(blockStorageClient, d.Id()).Extract() 180 if err != nil { 181 return CheckDeleted(d, err, "volume") 182 } 183 184 log.Printf("[DEBUG] Retrieved volume %s: %+v", d.Id(), v) 185 186 d.Set("size", v.Size) 187 d.Set("description", v.Description) 188 d.Set("availability_zone", v.AvailabilityZone) 189 d.Set("name", v.Name) 190 d.Set("snapshot_id", v.SnapshotID) 191 d.Set("source_vol_id", v.SourceVolID) 192 d.Set("volume_type", v.VolumeType) 193 d.Set("metadata", v.Metadata) 194 195 attachments := make([]map[string]interface{}, len(v.Attachments)) 196 for i, attachment := range v.Attachments { 197 attachments[i] = make(map[string]interface{}) 198 attachments[i]["id"] = attachment.ID 199 attachments[i]["instance_id"] = attachment.ServerID 200 attachments[i]["device"] = attachment.Device 201 log.Printf("[DEBUG] attachment: %v", attachment) 202 } 203 d.Set("attachment", attachments) 204 205 return nil 206 } 207 208 func resourceBlockStorageVolumeV2Update(d *schema.ResourceData, meta interface{}) error { 209 config := meta.(*Config) 210 blockStorageClient, err := config.blockStorageV2Client(d.Get("region").(string)) 211 if err != nil { 212 return fmt.Errorf("Error creating OpenStack block storage client: %s", err) 213 } 214 215 updateOpts := volumes.UpdateOpts{ 216 Name: d.Get("name").(string), 217 Description: d.Get("description").(string), 218 } 219 220 if d.HasChange("metadata") { 221 updateOpts.Metadata = resourceVolumeMetadataV2(d) 222 } 223 224 _, err = volumes.Update(blockStorageClient, d.Id(), updateOpts).Extract() 225 if err != nil { 226 return fmt.Errorf("Error updating OpenStack volume: %s", err) 227 } 228 229 return resourceBlockStorageVolumeV2Read(d, meta) 230 } 231 232 func resourceBlockStorageVolumeV2Delete(d *schema.ResourceData, meta interface{}) error { 233 config := meta.(*Config) 234 blockStorageClient, err := config.blockStorageV2Client(d.Get("region").(string)) 235 if err != nil { 236 return fmt.Errorf("Error creating OpenStack block storage client: %s", err) 237 } 238 239 v, err := volumes.Get(blockStorageClient, d.Id()).Extract() 240 if err != nil { 241 return CheckDeleted(d, err, "volume") 242 } 243 244 // make sure this volume is detached from all instances before deleting 245 if len(v.Attachments) > 0 { 246 log.Printf("[DEBUG] detaching volumes") 247 if computeClient, err := config.computeV2Client(d.Get("region").(string)); err != nil { 248 return err 249 } else { 250 for _, volumeAttachment := range v.Attachments { 251 log.Printf("[DEBUG] Attachment: %v", volumeAttachment) 252 if err := volumeattach.Delete(computeClient, volumeAttachment.ServerID, volumeAttachment.ID).ExtractErr(); err != nil { 253 return err 254 } 255 } 256 257 stateConf := &resource.StateChangeConf{ 258 Pending: []string{"in-use", "attaching", "detaching"}, 259 Target: []string{"available"}, 260 Refresh: VolumeV2StateRefreshFunc(blockStorageClient, d.Id()), 261 Timeout: 10 * time.Minute, 262 Delay: 10 * time.Second, 263 MinTimeout: 3 * time.Second, 264 } 265 266 _, err = stateConf.WaitForState() 267 if err != nil { 268 return fmt.Errorf( 269 "Error waiting for volume (%s) to become available: %s", 270 d.Id(), err) 271 } 272 } 273 } 274 275 // It's possible that this volume was used as a boot device and is currently 276 // in a "deleting" state from when the instance was terminated. 277 // If this is true, just move on. It'll eventually delete. 278 if v.Status != "deleting" { 279 if err := volumes.Delete(blockStorageClient, d.Id()).ExtractErr(); err != nil { 280 return CheckDeleted(d, err, "volume") 281 } 282 } 283 284 // Wait for the volume to delete before moving on. 285 log.Printf("[DEBUG] Waiting for volume (%s) to delete", d.Id()) 286 287 stateConf := &resource.StateChangeConf{ 288 Pending: []string{"deleting", "downloading", "available"}, 289 Target: []string{"deleted"}, 290 Refresh: VolumeV2StateRefreshFunc(blockStorageClient, d.Id()), 291 Timeout: 10 * time.Minute, 292 Delay: 10 * time.Second, 293 MinTimeout: 3 * time.Second, 294 } 295 296 _, err = stateConf.WaitForState() 297 if err != nil { 298 return fmt.Errorf( 299 "Error waiting for volume (%s) to delete: %s", 300 d.Id(), err) 301 } 302 303 d.SetId("") 304 return nil 305 } 306 307 func resourceVolumeMetadataV2(d *schema.ResourceData) map[string]string { 308 m := make(map[string]string) 309 for key, val := range d.Get("metadata").(map[string]interface{}) { 310 m[key] = val.(string) 311 } 312 return m 313 } 314 315 // VolumeV2StateRefreshFunc returns a resource.StateRefreshFunc that is used to watch 316 // an OpenStack volume. 317 func VolumeV2StateRefreshFunc(client *gophercloud.ServiceClient, volumeID string) resource.StateRefreshFunc { 318 return func() (interface{}, string, error) { 319 v, err := volumes.Get(client, volumeID).Extract() 320 if err != nil { 321 if _, ok := err.(gophercloud.ErrDefault404); ok { 322 return v, "deleted", nil 323 } 324 return nil, "", err 325 } 326 327 return v, v.Status, nil 328 } 329 } 330 331 func resourceVolumeV2AttachmentHash(v interface{}) int { 332 var buf bytes.Buffer 333 m := v.(map[string]interface{}) 334 if m["instance_id"] != nil { 335 buf.WriteString(fmt.Sprintf("%s-", m["instance_id"].(string))) 336 } 337 return hashcode.String(buf.String()) 338 }