github.com/recobe182/terraform@v0.8.5-0.20170117231232-49ab22a935b7/builtin/providers/openstack/resource_openstack_blockstorage_volume_attach_v2.go (about) 1 package openstack 2 3 import ( 4 "fmt" 5 "log" 6 "strings" 7 "time" 8 9 "github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions" 10 "github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes" 11 "github.com/hashicorp/terraform/helper/resource" 12 "github.com/hashicorp/terraform/helper/schema" 13 ) 14 15 func resourceBlockStorageVolumeAttachV2() *schema.Resource { 16 return &schema.Resource{ 17 Create: resourceBlockStorageVolumeAttachV2Create, 18 Read: resourceBlockStorageVolumeAttachV2Read, 19 Delete: resourceBlockStorageVolumeAttachV2Delete, 20 Importer: &schema.ResourceImporter{ 21 State: schema.ImportStatePassthrough, 22 }, 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 32 "volume_id": &schema.Schema{ 33 Type: schema.TypeString, 34 Required: true, 35 ForceNew: true, 36 }, 37 38 "instance_id": &schema.Schema{ 39 Type: schema.TypeString, 40 Optional: true, 41 ForceNew: true, 42 ConflictsWith: []string{"host_name"}, 43 }, 44 45 "host_name": &schema.Schema{ 46 Type: schema.TypeString, 47 Optional: true, 48 ForceNew: true, 49 ConflictsWith: []string{"instance_id"}, 50 }, 51 52 "device": &schema.Schema{ 53 Type: schema.TypeString, 54 Optional: true, 55 Computed: true, 56 }, 57 58 "attach_mode": &schema.Schema{ 59 Type: schema.TypeString, 60 Optional: true, 61 ForceNew: true, 62 ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { 63 value := v.(string) 64 if value != "ro" && value != "rw" { 65 errors = append(errors, fmt.Errorf( 66 "Only 'ro' and 'rw' are supported values for 'attach_mode'")) 67 } 68 return 69 }, 70 }, 71 }, 72 } 73 } 74 75 func resourceBlockStorageVolumeAttachV2Create(d *schema.ResourceData, meta interface{}) error { 76 config := meta.(*Config) 77 client, err := config.blockStorageV2Client(GetRegion(d)) 78 if err != nil { 79 return fmt.Errorf("Error creating OpenStack block storage client: %s", err) 80 } 81 82 // Check if either instance_id or host_name was set. 83 instanceId := d.Get("instance_id").(string) 84 hostName := d.Get("host_name").(string) 85 if instanceId == "" && hostName == "" { 86 return fmt.Errorf("One of 'instance_id' or 'host_name' must be set.") 87 } 88 89 volumeId := d.Get("volume_id").(string) 90 91 attachMode, err := blockStorageVolumeAttachV2AttachMode(d.Get("attach_mode").(string)) 92 if err != nil { 93 return nil 94 } 95 96 attachOpts := &volumeactions.AttachOpts{ 97 InstanceUUID: d.Get("instance_id").(string), 98 HostName: d.Get("host_name").(string), 99 MountPoint: d.Get("device").(string), 100 Mode: attachMode, 101 } 102 103 log.Printf("[DEBUG] Attachment Options: %#v", attachOpts) 104 105 if err := volumeactions.Attach(client, volumeId, attachOpts).ExtractErr(); err != nil { 106 return err 107 } 108 109 // Wait for the volume to become available. 110 log.Printf("[DEBUG] Waiting for volume (%s) to become available", volumeId) 111 112 stateConf := &resource.StateChangeConf{ 113 Pending: []string{"available", "attaching"}, 114 Target: []string{"in-use"}, 115 Refresh: VolumeV2StateRefreshFunc(client, volumeId), 116 Timeout: 10 * time.Minute, 117 Delay: 10 * time.Second, 118 MinTimeout: 3 * time.Second, 119 } 120 121 _, err = stateConf.WaitForState() 122 if err != nil { 123 return fmt.Errorf("Error waiting for volume (%s) to become ready: %s", volumeId, err) 124 } 125 126 volume, err := volumes.Get(client, volumeId).Extract() 127 if err != nil { 128 return err 129 } 130 131 var attachmentId string 132 for _, attachment := range volume.Attachments { 133 if instanceId != "" && instanceId == attachment.ServerID { 134 attachmentId = attachment.AttachmentID 135 } 136 137 if hostName != "" && hostName == attachment.HostName { 138 attachmentId = attachment.AttachmentID 139 } 140 } 141 142 if attachmentId == "" { 143 return fmt.Errorf("Unable to determine attachment ID.") 144 } 145 146 // The ID must be a combination of the volume and attachment ID 147 // in order to import attachments. 148 id := fmt.Sprintf("%s/%s", volumeId, attachmentId) 149 d.SetId(id) 150 151 return resourceBlockStorageVolumeAttachV2Read(d, meta) 152 } 153 154 func resourceBlockStorageVolumeAttachV2Read(d *schema.ResourceData, meta interface{}) error { 155 config := meta.(*Config) 156 client, err := config.blockStorageV2Client(GetRegion(d)) 157 if err != nil { 158 return fmt.Errorf("Error creating OpenStack block storage client: %s", err) 159 } 160 161 volumeId, attachmentId, err := blockStorageVolumeAttachV2ParseId(d.Id()) 162 if err != nil { 163 return err 164 } 165 166 volume, err := volumes.Get(client, volumeId).Extract() 167 if err != nil { 168 return err 169 } 170 171 log.Printf("[DEBUG] Retrieved volume %s: %#v", d.Id(), volume) 172 173 var attachment volumes.Attachment 174 for _, v := range volume.Attachments { 175 if attachmentId == v.AttachmentID { 176 attachment = v 177 } 178 } 179 180 log.Printf("[DEBUG] Retrieved volume attachment: %#v", attachment) 181 182 d.Set("volume_id", volumeId) 183 d.Set("attachment_id", attachmentId) 184 d.Set("device", attachment.Device) 185 d.Set("instance_id", attachment.ServerID) 186 d.Set("host_name", attachment.HostName) 187 d.Set("region", GetRegion(d)) 188 189 return nil 190 } 191 192 func resourceBlockStorageVolumeAttachV2Delete(d *schema.ResourceData, meta interface{}) error { 193 config := meta.(*Config) 194 client, err := config.blockStorageV2Client(GetRegion(d)) 195 if err != nil { 196 return fmt.Errorf("Error creating OpenStack block storage client: %s", err) 197 } 198 199 volumeId, attachmentId, err := blockStorageVolumeAttachV2ParseId(d.Id()) 200 if err != nil { 201 return err 202 } 203 204 detachOpts := volumeactions.DetachOpts{ 205 AttachmentID: attachmentId, 206 } 207 208 log.Printf("[DEBUG] Detachment Options: %#v", detachOpts) 209 210 if err := volumeactions.Detach(client, volumeId, detachOpts).ExtractErr(); err != nil { 211 return err 212 } 213 214 stateConf := &resource.StateChangeConf{ 215 Pending: []string{"in-use", "attaching", "detaching"}, 216 Target: []string{"available"}, 217 Refresh: VolumeV2StateRefreshFunc(client, volumeId), 218 Timeout: 10 * time.Minute, 219 Delay: 10 * time.Second, 220 MinTimeout: 3 * time.Second, 221 } 222 223 _, err = stateConf.WaitForState() 224 if err != nil { 225 return fmt.Errorf("Error waiting for volume (%s) to become available: %s", volumeId, err) 226 } 227 228 return nil 229 } 230 231 func blockStorageVolumeAttachV2AttachMode(v string) (volumeactions.AttachMode, error) { 232 var attachMode volumeactions.AttachMode 233 var attachError error 234 switch v { 235 case "": 236 attachMode = "" 237 case "ro": 238 attachMode = volumeactions.ReadOnly 239 case "rw": 240 attachMode = volumeactions.ReadWrite 241 default: 242 attachError = fmt.Errorf("Invalid attach_mode specified") 243 } 244 245 return attachMode, attachError 246 } 247 248 func blockStorageVolumeAttachV2ParseId(id string) (string, string, error) { 249 parts := strings.Split(id, "/") 250 if len(parts) < 2 { 251 return "", "", fmt.Errorf("Unable to determine attachment ID") 252 } 253 254 return parts[0], parts[1], nil 255 }