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