github.com/danp/terraform@v0.9.5-0.20170426144147-39d740081351/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 12 "github.com/hashicorp/terraform/helper/resource" 13 "github.com/hashicorp/terraform/helper/schema" 14 ) 15 16 func resourceBlockStorageVolumeAttachV2() *schema.Resource { 17 return &schema.Resource{ 18 Create: resourceBlockStorageVolumeAttachV2Create, 19 Read: resourceBlockStorageVolumeAttachV2Read, 20 Delete: resourceBlockStorageVolumeAttachV2Delete, 21 22 Timeouts: &schema.ResourceTimeout{ 23 Create: schema.DefaultTimeout(10 * time.Minute), 24 Delete: schema.DefaultTimeout(10 * time.Minute), 25 }, 26 27 Schema: map[string]*schema.Schema{ 28 "region": &schema.Schema{ 29 Type: schema.TypeString, 30 Required: true, 31 ForceNew: true, 32 DefaultFunc: schema.EnvDefaultFunc("OS_REGION_NAME", ""), 33 }, 34 35 "volume_id": &schema.Schema{ 36 Type: schema.TypeString, 37 Required: true, 38 ForceNew: true, 39 }, 40 41 "instance_id": &schema.Schema{ 42 Type: schema.TypeString, 43 Optional: true, 44 ForceNew: true, 45 Deprecated: "instance_id is no longer used in this resource", 46 }, 47 48 "host_name": &schema.Schema{ 49 Type: schema.TypeString, 50 Required: true, 51 ForceNew: true, 52 }, 53 54 "device": &schema.Schema{ 55 Type: schema.TypeString, 56 Optional: true, 57 ForceNew: true, 58 }, 59 60 "attach_mode": &schema.Schema{ 61 Type: schema.TypeString, 62 Optional: true, 63 ForceNew: true, 64 ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { 65 value := v.(string) 66 if value != "ro" && value != "rw" { 67 errors = append(errors, fmt.Errorf( 68 "Only 'ro' and 'rw' are supported values for 'attach_mode'")) 69 } 70 return 71 }, 72 }, 73 74 "initiator": &schema.Schema{ 75 Type: schema.TypeString, 76 Optional: true, 77 ForceNew: true, 78 }, 79 80 "ip_address": &schema.Schema{ 81 Type: schema.TypeString, 82 Optional: true, 83 ForceNew: true, 84 }, 85 86 "multipath": &schema.Schema{ 87 Type: schema.TypeBool, 88 Optional: true, 89 ForceNew: true, 90 }, 91 92 "os_type": &schema.Schema{ 93 Type: schema.TypeString, 94 Optional: true, 95 ForceNew: true, 96 }, 97 98 "platform": &schema.Schema{ 99 Type: schema.TypeString, 100 Optional: true, 101 ForceNew: true, 102 }, 103 104 "wwpn": &schema.Schema{ 105 Type: schema.TypeList, 106 Optional: true, 107 ForceNew: true, 108 Elem: &schema.Schema{Type: schema.TypeString}, 109 }, 110 111 "wwnn": &schema.Schema{ 112 Type: schema.TypeString, 113 Optional: true, 114 ForceNew: true, 115 }, 116 117 // Volume attachment information 118 "data": &schema.Schema{ 119 Type: schema.TypeMap, 120 Computed: true, 121 Sensitive: true, 122 }, 123 124 "driver_volume_type": &schema.Schema{ 125 Type: schema.TypeString, 126 Computed: true, 127 }, 128 129 "mount_point_base": &schema.Schema{ 130 Type: schema.TypeString, 131 Computed: true, 132 }, 133 }, 134 } 135 } 136 137 func resourceBlockStorageVolumeAttachV2Create(d *schema.ResourceData, meta interface{}) error { 138 config := meta.(*Config) 139 client, err := config.blockStorageV2Client(GetRegion(d)) 140 if err != nil { 141 return fmt.Errorf("Error creating OpenStack block storage client: %s", err) 142 } 143 144 // initialize the connection 145 volumeId := d.Get("volume_id").(string) 146 connOpts := &volumeactions.InitializeConnectionOpts{} 147 if v, ok := d.GetOk("host_name"); ok { 148 connOpts.Host = v.(string) 149 } 150 151 if v, ok := d.GetOk("multipath"); ok { 152 multipath := v.(bool) 153 connOpts.Multipath = &multipath 154 } 155 156 if v, ok := d.GetOk("ip_address"); ok { 157 connOpts.IP = v.(string) 158 } 159 160 if v, ok := d.GetOk("initiator"); ok { 161 connOpts.Initiator = v.(string) 162 } 163 164 if v, ok := d.GetOk("os_type"); ok { 165 connOpts.OSType = v.(string) 166 } 167 168 if v, ok := d.GetOk("platform"); ok { 169 connOpts.Platform = v.(string) 170 } 171 172 if v, ok := d.GetOk("wwnns"); ok { 173 connOpts.Wwnns = v.(string) 174 } 175 176 if v, ok := d.GetOk("wwpns"); ok { 177 var wwpns []string 178 for _, i := range v.([]string) { 179 wwpns = append(wwpns, i) 180 } 181 182 connOpts.Wwpns = wwpns 183 } 184 185 connInfo, err := volumeactions.InitializeConnection(client, volumeId, connOpts).Extract() 186 if err != nil { 187 return fmt.Errorf("Unable to create connection: %s", err) 188 } 189 190 // Only uncomment this when debugging since connInfo contains sensitive information. 191 // log.Printf("[DEBUG] Volume Connection for %s: %#v", volumeId, connInfo) 192 193 // Because this information is only returned upon creation, 194 // it must be set in Create. 195 if v, ok := connInfo["data"]; ok { 196 data := make(map[string]string) 197 for key, value := range v.(map[string]interface{}) { 198 if v, ok := value.(string); ok { 199 data[key] = v 200 } 201 } 202 203 d.Set("data", data) 204 } 205 206 if v, ok := connInfo["driver_volume_type"]; ok { 207 d.Set("driver_volume_type", v) 208 } 209 210 if v, ok := connInfo["mount_point_base"]; ok { 211 d.Set("mount_point_base", v) 212 } 213 214 // Once the connection has been made, tell Cinder to mark the volume as attached. 215 attachMode, err := blockStorageVolumeAttachV2AttachMode(d.Get("attach_mode").(string)) 216 if err != nil { 217 return nil 218 } 219 220 attachOpts := &volumeactions.AttachOpts{ 221 HostName: d.Get("host_name").(string), 222 MountPoint: d.Get("device").(string), 223 Mode: attachMode, 224 } 225 226 log.Printf("[DEBUG] Attachment Options: %#v", attachOpts) 227 228 if err := volumeactions.Attach(client, volumeId, attachOpts).ExtractErr(); err != nil { 229 return err 230 } 231 232 // Wait for the volume to become available. 233 log.Printf("[DEBUG] Waiting for volume (%s) to become available", volumeId) 234 235 stateConf := &resource.StateChangeConf{ 236 Pending: []string{"available", "attaching"}, 237 Target: []string{"in-use"}, 238 Refresh: VolumeV2StateRefreshFunc(client, volumeId), 239 Timeout: d.Timeout(schema.TimeoutCreate), 240 Delay: 10 * time.Second, 241 MinTimeout: 3 * time.Second, 242 } 243 244 _, err = stateConf.WaitForState() 245 if err != nil { 246 return fmt.Errorf("Error waiting for volume (%s) to become ready: %s", volumeId, err) 247 } 248 249 // Once the volume has been marked as attached, 250 // retrieve a fresh copy of it with all information now available. 251 volume, err := volumes.Get(client, volumeId).Extract() 252 if err != nil { 253 return err 254 } 255 256 // Search for the attachmentId 257 var attachmentId string 258 hostName := d.Get("host_name").(string) 259 for _, attachment := range volume.Attachments { 260 if hostName != "" && hostName == attachment.HostName { 261 attachmentId = attachment.AttachmentID 262 } 263 } 264 265 if attachmentId == "" { 266 return fmt.Errorf("Unable to determine attachment ID.") 267 } 268 269 // The ID must be a combination of the volume and attachment ID 270 // since a volume ID is required to retrieve an attachment ID. 271 id := fmt.Sprintf("%s/%s", volumeId, attachmentId) 272 d.SetId(id) 273 274 return resourceBlockStorageVolumeAttachV2Read(d, meta) 275 } 276 277 func resourceBlockStorageVolumeAttachV2Read(d *schema.ResourceData, meta interface{}) error { 278 config := meta.(*Config) 279 client, err := config.blockStorageV2Client(GetRegion(d)) 280 if err != nil { 281 return fmt.Errorf("Error creating OpenStack block storage client: %s", err) 282 } 283 284 volumeId, attachmentId, err := blockStorageVolumeAttachV2ParseId(d.Id()) 285 if err != nil { 286 return err 287 } 288 289 volume, err := volumes.Get(client, volumeId).Extract() 290 if err != nil { 291 return err 292 } 293 294 log.Printf("[DEBUG] Retrieved volume %s: %#v", d.Id(), volume) 295 296 var attachment volumes.Attachment 297 for _, v := range volume.Attachments { 298 if attachmentId == v.AttachmentID { 299 attachment = v 300 } 301 } 302 303 log.Printf("[DEBUG] Retrieved volume attachment: %#v", attachment) 304 305 return nil 306 } 307 308 func resourceBlockStorageVolumeAttachV2Delete(d *schema.ResourceData, meta interface{}) error { 309 config := meta.(*Config) 310 client, err := config.blockStorageV2Client(GetRegion(d)) 311 if err != nil { 312 return fmt.Errorf("Error creating OpenStack block storage client: %s", err) 313 } 314 315 volumeId, attachmentId, err := blockStorageVolumeAttachV2ParseId(d.Id()) 316 317 // Terminate the connection 318 termOpts := &volumeactions.TerminateConnectionOpts{} 319 if v, ok := d.GetOk("host_name"); ok { 320 termOpts.Host = v.(string) 321 } 322 323 if v, ok := d.GetOk("multipath"); ok { 324 multipath := v.(bool) 325 termOpts.Multipath = &multipath 326 } 327 328 if v, ok := d.GetOk("ip_address"); ok { 329 termOpts.IP = v.(string) 330 } 331 332 if v, ok := d.GetOk("initiator"); ok { 333 termOpts.Initiator = v.(string) 334 } 335 336 if v, ok := d.GetOk("os_type"); ok { 337 termOpts.OSType = v.(string) 338 } 339 340 if v, ok := d.GetOk("platform"); ok { 341 termOpts.Platform = v.(string) 342 } 343 344 if v, ok := d.GetOk("wwnns"); ok { 345 termOpts.Wwnns = v.(string) 346 } 347 348 if v, ok := d.GetOk("wwpns"); ok { 349 var wwpns []string 350 for _, i := range v.([]string) { 351 wwpns = append(wwpns, i) 352 } 353 354 termOpts.Wwpns = wwpns 355 } 356 357 err = volumeactions.TerminateConnection(client, volumeId, termOpts).ExtractErr() 358 if err != nil { 359 return fmt.Errorf("Error terminating volume connection %s: %s", volumeId, err) 360 } 361 362 // Detach the volume 363 detachOpts := volumeactions.DetachOpts{ 364 AttachmentID: attachmentId, 365 } 366 367 log.Printf("[DEBUG] Detachment Options: %#v", detachOpts) 368 369 if err := volumeactions.Detach(client, volumeId, detachOpts).ExtractErr(); err != nil { 370 return err 371 } 372 373 stateConf := &resource.StateChangeConf{ 374 Pending: []string{"in-use", "attaching", "detaching"}, 375 Target: []string{"available"}, 376 Refresh: VolumeV2StateRefreshFunc(client, volumeId), 377 Timeout: d.Timeout(schema.TimeoutDelete), 378 Delay: 10 * time.Second, 379 MinTimeout: 3 * time.Second, 380 } 381 382 _, err = stateConf.WaitForState() 383 if err != nil { 384 return fmt.Errorf("Error waiting for volume (%s) to become available: %s", volumeId, err) 385 } 386 387 return nil 388 } 389 390 func blockStorageVolumeAttachV2AttachMode(v string) (volumeactions.AttachMode, error) { 391 var attachMode volumeactions.AttachMode 392 var attachError error 393 switch v { 394 case "": 395 attachMode = "" 396 case "ro": 397 attachMode = volumeactions.ReadOnly 398 case "rw": 399 attachMode = volumeactions.ReadWrite 400 default: 401 attachError = fmt.Errorf("Invalid attach_mode specified") 402 } 403 404 return attachMode, attachError 405 } 406 407 func blockStorageVolumeAttachV2ParseId(id string) (string, string, error) { 408 parts := strings.Split(id, "/") 409 if len(parts) < 2 { 410 return "", "", fmt.Errorf("Unable to determine attachment ID") 411 } 412 413 return parts[0], parts[1], nil 414 }