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