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