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