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