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