github.com/recobe182/terraform@v0.8.5-0.20170117231232-49ab22a935b7/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(GetRegion(d)) 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 // Wait for the volume to become available. 146 log.Printf( 147 "[DEBUG] Waiting for volume (%s) to become available", 148 v.ID) 149 150 stateConf := &resource.StateChangeConf{ 151 Pending: []string{"downloading", "creating"}, 152 Target: []string{"available"}, 153 Refresh: VolumeV2StateRefreshFunc(blockStorageClient, v.ID), 154 Timeout: 10 * time.Minute, 155 Delay: 10 * time.Second, 156 MinTimeout: 3 * time.Second, 157 } 158 159 _, err = stateConf.WaitForState() 160 if err != nil { 161 return fmt.Errorf( 162 "Error waiting for volume (%s) to become ready: %s", 163 v.ID, err) 164 } 165 166 // Store the ID now 167 d.SetId(v.ID) 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(GetRegion(d)) 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 d.Set("region", GetRegion(d)) 195 196 attachments := make([]map[string]interface{}, len(v.Attachments)) 197 for i, attachment := range v.Attachments { 198 attachments[i] = make(map[string]interface{}) 199 attachments[i]["id"] = attachment.ID 200 attachments[i]["instance_id"] = attachment.ServerID 201 attachments[i]["device"] = attachment.Device 202 log.Printf("[DEBUG] attachment: %v", attachment) 203 } 204 d.Set("attachment", attachments) 205 206 return nil 207 } 208 209 func resourceBlockStorageVolumeV2Update(d *schema.ResourceData, meta interface{}) error { 210 config := meta.(*Config) 211 blockStorageClient, err := config.blockStorageV2Client(GetRegion(d)) 212 if err != nil { 213 return fmt.Errorf("Error creating OpenStack block storage client: %s", err) 214 } 215 216 updateOpts := volumes.UpdateOpts{ 217 Name: d.Get("name").(string), 218 Description: d.Get("description").(string), 219 } 220 221 if d.HasChange("metadata") { 222 updateOpts.Metadata = resourceVolumeMetadataV2(d) 223 } 224 225 _, err = volumes.Update(blockStorageClient, d.Id(), updateOpts).Extract() 226 if err != nil { 227 return fmt.Errorf("Error updating OpenStack volume: %s", err) 228 } 229 230 return resourceBlockStorageVolumeV2Read(d, meta) 231 } 232 233 func resourceBlockStorageVolumeV2Delete(d *schema.ResourceData, meta interface{}) error { 234 config := meta.(*Config) 235 blockStorageClient, err := config.blockStorageV2Client(GetRegion(d)) 236 if err != nil { 237 return fmt.Errorf("Error creating OpenStack block storage client: %s", err) 238 } 239 240 v, err := volumes.Get(blockStorageClient, d.Id()).Extract() 241 if err != nil { 242 return CheckDeleted(d, err, "volume") 243 } 244 245 // make sure this volume is detached from all instances before deleting 246 if len(v.Attachments) > 0 { 247 log.Printf("[DEBUG] detaching volumes") 248 if computeClient, err := config.computeV2Client(GetRegion(d)); err != nil { 249 return err 250 } else { 251 for _, volumeAttachment := range v.Attachments { 252 log.Printf("[DEBUG] Attachment: %v", volumeAttachment) 253 if err := volumeattach.Delete(computeClient, volumeAttachment.ServerID, volumeAttachment.ID).ExtractErr(); err != nil { 254 return err 255 } 256 } 257 258 stateConf := &resource.StateChangeConf{ 259 Pending: []string{"in-use", "attaching", "detaching"}, 260 Target: []string{"available"}, 261 Refresh: VolumeV2StateRefreshFunc(blockStorageClient, d.Id()), 262 Timeout: 10 * time.Minute, 263 Delay: 10 * time.Second, 264 MinTimeout: 3 * time.Second, 265 } 266 267 _, err = stateConf.WaitForState() 268 if err != nil { 269 return fmt.Errorf( 270 "Error waiting for volume (%s) to become available: %s", 271 d.Id(), err) 272 } 273 } 274 } 275 276 // It's possible that this volume was used as a boot device and is currently 277 // in a "deleting" state from when the instance was terminated. 278 // If this is true, just move on. It'll eventually delete. 279 if v.Status != "deleting" { 280 if err := volumes.Delete(blockStorageClient, d.Id()).ExtractErr(); err != nil { 281 return CheckDeleted(d, err, "volume") 282 } 283 } 284 285 // Wait for the volume to delete before moving on. 286 log.Printf("[DEBUG] Waiting for volume (%s) to delete", d.Id()) 287 288 stateConf := &resource.StateChangeConf{ 289 Pending: []string{"deleting", "downloading", "available"}, 290 Target: []string{"deleted"}, 291 Refresh: VolumeV2StateRefreshFunc(blockStorageClient, d.Id()), 292 Timeout: 10 * time.Minute, 293 Delay: 10 * time.Second, 294 MinTimeout: 3 * time.Second, 295 } 296 297 _, err = stateConf.WaitForState() 298 if err != nil { 299 return fmt.Errorf( 300 "Error waiting for volume (%s) to delete: %s", 301 d.Id(), err) 302 } 303 304 d.SetId("") 305 return nil 306 } 307 308 func resourceVolumeMetadataV2(d *schema.ResourceData) map[string]string { 309 m := make(map[string]string) 310 for key, val := range d.Get("metadata").(map[string]interface{}) { 311 m[key] = val.(string) 312 } 313 return m 314 } 315 316 // VolumeV2StateRefreshFunc returns a resource.StateRefreshFunc that is used to watch 317 // an OpenStack volume. 318 func VolumeV2StateRefreshFunc(client *gophercloud.ServiceClient, volumeID string) resource.StateRefreshFunc { 319 return func() (interface{}, string, error) { 320 v, err := volumes.Get(client, volumeID).Extract() 321 if err != nil { 322 if _, ok := err.(gophercloud.ErrDefault404); ok { 323 return v, "deleted", nil 324 } 325 return nil, "", err 326 } 327 328 if v.Status == "error" { 329 return v, v.Status, fmt.Errorf("There was an error creating the volume. " + 330 "Please check with your cloud admin or check the Block Storage " + 331 "API logs to see why this error occurred.") 332 } 333 334 return v, v.Status, nil 335 } 336 } 337 338 func resourceVolumeV2AttachmentHash(v interface{}) int { 339 var buf bytes.Buffer 340 m := v.(map[string]interface{}) 341 if m["instance_id"] != nil { 342 buf.WriteString(fmt.Sprintf("%s-", m["instance_id"].(string))) 343 } 344 return hashcode.String(buf.String()) 345 }