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