github.com/turtlemonvh/terraform@v0.6.9-0.20151204001754-8e40b6b855e8/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/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/v1/volumes" 14 "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/volumeattach" 15 ) 16 17 func resourceBlockStorageVolumeV1() *schema.Resource { 18 return &schema.Resource{ 19 Create: resourceBlockStorageVolumeV1Create, 20 Read: resourceBlockStorageVolumeV1Read, 21 Update: resourceBlockStorageVolumeV1Update, 22 Delete: resourceBlockStorageVolumeV1Delete, 23 24 Schema: map[string]*schema.Schema{ 25 "region": &schema.Schema{ 26 Type: schema.TypeString, 27 Required: true, 28 ForceNew: true, 29 DefaultFunc: envDefaultFuncAllowMissing("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 "attachment": &schema.Schema{ 80 Type: schema.TypeSet, 81 Computed: true, 82 Elem: &schema.Resource{ 83 Schema: map[string]*schema.Schema{ 84 "id": &schema.Schema{ 85 Type: schema.TypeString, 86 Computed: true, 87 }, 88 "instance_id": &schema.Schema{ 89 Type: schema.TypeString, 90 Computed: true, 91 }, 92 "device": &schema.Schema{ 93 Type: schema.TypeString, 94 Computed: true, 95 }, 96 }, 97 }, 98 Set: resourceVolumeAttachmentHash, 99 }, 100 }, 101 } 102 } 103 104 func resourceBlockStorageVolumeV1Create(d *schema.ResourceData, meta interface{}) error { 105 config := meta.(*Config) 106 blockStorageClient, err := config.blockStorageV1Client(d.Get("region").(string)) 107 if err != nil { 108 return fmt.Errorf("Error creating OpenStack block storage client: %s", err) 109 } 110 111 createOpts := &volumes.CreateOpts{ 112 Description: d.Get("description").(string), 113 Availability: d.Get("availability_zone").(string), 114 Name: d.Get("name").(string), 115 Size: d.Get("size").(int), 116 SnapshotID: d.Get("snapshot_id").(string), 117 SourceVolID: d.Get("source_vol_id").(string), 118 ImageID: d.Get("image_id").(string), 119 VolumeType: d.Get("volume_type").(string), 120 Metadata: resourceContainerMetadataV2(d), 121 } 122 123 log.Printf("[DEBUG] Create Options: %#v", createOpts) 124 v, err := volumes.Create(blockStorageClient, createOpts).Extract() 125 if err != nil { 126 return fmt.Errorf("Error creating OpenStack volume: %s", err) 127 } 128 log.Printf("[INFO] Volume ID: %s", v.ID) 129 130 // Store the ID now 131 d.SetId(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: "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 return resourceBlockStorageVolumeV1Read(d, meta) 155 } 156 157 func resourceBlockStorageVolumeV1Read(d *schema.ResourceData, meta interface{}) error { 158 config := meta.(*Config) 159 blockStorageClient, err := config.blockStorageV1Client(d.Get("region").(string)) 160 if err != nil { 161 return fmt.Errorf("Error creating OpenStack block storage client: %s", err) 162 } 163 164 v, err := volumes.Get(blockStorageClient, d.Id()).Extract() 165 if err != nil { 166 return CheckDeleted(d, err, "volume") 167 } 168 169 log.Printf("[DEBUG] Retreived volume %s: %+v", d.Id(), v) 170 171 d.Set("size", v.Size) 172 d.Set("description", v.Description) 173 d.Set("availability_zone", v.AvailabilityZone) 174 d.Set("name", v.Name) 175 d.Set("snapshot_id", v.SnapshotID) 176 d.Set("source_vol_id", v.SourceVolID) 177 d.Set("volume_type", v.VolumeType) 178 d.Set("metadata", v.Metadata) 179 180 if len(v.Attachments) > 0 { 181 attachments := make([]map[string]interface{}, len(v.Attachments)) 182 for i, attachment := range v.Attachments { 183 attachments[i] = make(map[string]interface{}) 184 attachments[i]["id"] = attachment["id"] 185 attachments[i]["instance_id"] = attachment["server_id"] 186 attachments[i]["device"] = attachment["device"] 187 log.Printf("[DEBUG] attachment: %v", attachment) 188 } 189 d.Set("attachment", attachments) 190 } 191 192 return nil 193 } 194 195 func resourceBlockStorageVolumeV1Update(d *schema.ResourceData, meta interface{}) error { 196 config := meta.(*Config) 197 blockStorageClient, err := config.blockStorageV1Client(d.Get("region").(string)) 198 if err != nil { 199 return fmt.Errorf("Error creating OpenStack block storage client: %s", err) 200 } 201 202 updateOpts := volumes.UpdateOpts{ 203 Name: d.Get("name").(string), 204 Description: d.Get("description").(string), 205 } 206 207 if d.HasChange("metadata") { 208 updateOpts.Metadata = resourceVolumeMetadataV1(d) 209 } 210 211 _, err = volumes.Update(blockStorageClient, d.Id(), updateOpts).Extract() 212 if err != nil { 213 return fmt.Errorf("Error updating OpenStack volume: %s", err) 214 } 215 216 return resourceBlockStorageVolumeV1Read(d, meta) 217 } 218 219 func resourceBlockStorageVolumeV1Delete(d *schema.ResourceData, meta interface{}) error { 220 config := meta.(*Config) 221 blockStorageClient, err := config.blockStorageV1Client(d.Get("region").(string)) 222 if err != nil { 223 return fmt.Errorf("Error creating OpenStack block storage client: %s", err) 224 } 225 226 v, err := volumes.Get(blockStorageClient, d.Id()).Extract() 227 if err != nil { 228 return CheckDeleted(d, err, "volume") 229 } 230 231 // make sure this volume is detached from all instances before deleting 232 if len(v.Attachments) > 0 { 233 log.Printf("[DEBUG] detaching volumes") 234 if computeClient, err := config.computeV2Client(d.Get("region").(string)); err != nil { 235 return err 236 } else { 237 for _, volumeAttachment := range v.Attachments { 238 log.Printf("[DEBUG] Attachment: %v", volumeAttachment) 239 if err := volumeattach.Delete(computeClient, volumeAttachment["server_id"].(string), volumeAttachment["id"].(string)).ExtractErr(); err != nil { 240 return err 241 } 242 } 243 244 stateConf := &resource.StateChangeConf{ 245 Pending: []string{"in-use", "attaching"}, 246 Target: "available", 247 Refresh: VolumeV1StateRefreshFunc(blockStorageClient, d.Id()), 248 Timeout: 10 * time.Minute, 249 Delay: 10 * time.Second, 250 MinTimeout: 3 * time.Second, 251 } 252 253 _, err = stateConf.WaitForState() 254 if err != nil { 255 return fmt.Errorf( 256 "Error waiting for volume (%s) to become available: %s", 257 d.Id(), err) 258 } 259 } 260 } 261 262 err = volumes.Delete(blockStorageClient, d.Id()).ExtractErr() 263 if err != nil { 264 return fmt.Errorf("Error deleting OpenStack volume: %s", err) 265 } 266 267 // Wait for the volume to delete before moving on. 268 log.Printf("[DEBUG] Waiting for volume (%s) to delete", d.Id()) 269 270 stateConf := &resource.StateChangeConf{ 271 Pending: []string{"deleting", "downloading", "available"}, 272 Target: "deleted", 273 Refresh: VolumeV1StateRefreshFunc(blockStorageClient, d.Id()), 274 Timeout: 10 * time.Minute, 275 Delay: 10 * time.Second, 276 MinTimeout: 3 * time.Second, 277 } 278 279 _, err = stateConf.WaitForState() 280 if err != nil { 281 return fmt.Errorf( 282 "Error waiting for volume (%s) to delete: %s", 283 d.Id(), err) 284 } 285 286 d.SetId("") 287 return nil 288 } 289 290 func resourceVolumeMetadataV1(d *schema.ResourceData) map[string]string { 291 m := make(map[string]string) 292 for key, val := range d.Get("metadata").(map[string]interface{}) { 293 m[key] = val.(string) 294 } 295 return m 296 } 297 298 // VolumeV1StateRefreshFunc returns a resource.StateRefreshFunc that is used to watch 299 // an OpenStack volume. 300 func VolumeV1StateRefreshFunc(client *gophercloud.ServiceClient, volumeID string) resource.StateRefreshFunc { 301 return func() (interface{}, string, error) { 302 v, err := volumes.Get(client, volumeID).Extract() 303 if err != nil { 304 errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) 305 if !ok { 306 return nil, "", err 307 } 308 if errCode.Actual == 404 { 309 return v, "deleted", nil 310 } 311 return nil, "", err 312 } 313 314 return v, v.Status, nil 315 } 316 } 317 318 func resourceVolumeAttachmentHash(v interface{}) int { 319 var buf bytes.Buffer 320 m := v.(map[string]interface{}) 321 if m["instance_id"] != nil { 322 buf.WriteString(fmt.Sprintf("%s-", m["instance_id"].(string))) 323 } 324 return hashcode.String(buf.String()) 325 }