github.com/tarrant/terraform@v0.3.8-0.20150402012457-f68c9eee638e/builtin/providers/openstack/resource_openstack_compute_instance_v2.go (about) 1 package openstack 2 3 import ( 4 "bytes" 5 "crypto/sha1" 6 "encoding/hex" 7 "fmt" 8 "log" 9 "time" 10 11 "github.com/hashicorp/terraform/helper/hashcode" 12 "github.com/hashicorp/terraform/helper/resource" 13 "github.com/hashicorp/terraform/helper/schema" 14 "github.com/rackspace/gophercloud" 15 "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/bootfromvolume" 16 "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/keypairs" 17 "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/secgroups" 18 "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/volumeattach" 19 "github.com/rackspace/gophercloud/openstack/compute/v2/flavors" 20 "github.com/rackspace/gophercloud/openstack/compute/v2/images" 21 "github.com/rackspace/gophercloud/openstack/compute/v2/servers" 22 "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/floatingips" 23 "github.com/rackspace/gophercloud/openstack/networking/v2/ports" 24 "github.com/rackspace/gophercloud/pagination" 25 ) 26 27 func resourceComputeInstanceV2() *schema.Resource { 28 return &schema.Resource{ 29 Create: resourceComputeInstanceV2Create, 30 Read: resourceComputeInstanceV2Read, 31 Update: resourceComputeInstanceV2Update, 32 Delete: resourceComputeInstanceV2Delete, 33 34 Schema: map[string]*schema.Schema{ 35 "region": &schema.Schema{ 36 Type: schema.TypeString, 37 Required: true, 38 ForceNew: true, 39 DefaultFunc: envDefaultFunc("OS_REGION_NAME"), 40 }, 41 "name": &schema.Schema{ 42 Type: schema.TypeString, 43 Required: true, 44 ForceNew: false, 45 }, 46 "image_id": &schema.Schema{ 47 Type: schema.TypeString, 48 Optional: true, 49 ForceNew: true, 50 Computed: true, 51 DefaultFunc: envDefaultFunc("OS_IMAGE_ID"), 52 }, 53 "image_name": &schema.Schema{ 54 Type: schema.TypeString, 55 Optional: true, 56 ForceNew: true, 57 Computed: true, 58 DefaultFunc: envDefaultFunc("OS_IMAGE_NAME"), 59 }, 60 "flavor_id": &schema.Schema{ 61 Type: schema.TypeString, 62 Optional: true, 63 ForceNew: false, 64 Computed: true, 65 DefaultFunc: envDefaultFunc("OS_FLAVOR_ID"), 66 }, 67 "flavor_name": &schema.Schema{ 68 Type: schema.TypeString, 69 Optional: true, 70 ForceNew: false, 71 Computed: true, 72 DefaultFunc: envDefaultFunc("OS_FLAVOR_NAME"), 73 }, 74 "floating_ip": &schema.Schema{ 75 Type: schema.TypeString, 76 Optional: true, 77 ForceNew: false, 78 }, 79 "user_data": &schema.Schema{ 80 Type: schema.TypeString, 81 Optional: true, 82 ForceNew: true, 83 // just stash the hash for state & diff comparisons 84 StateFunc: func(v interface{}) string { 85 switch v.(type) { 86 case string: 87 hash := sha1.Sum([]byte(v.(string))) 88 return hex.EncodeToString(hash[:]) 89 default: 90 return "" 91 } 92 }, 93 }, 94 "security_groups": &schema.Schema{ 95 Type: schema.TypeList, 96 Optional: true, 97 ForceNew: false, 98 Elem: &schema.Schema{Type: schema.TypeString}, 99 }, 100 "availability_zone": &schema.Schema{ 101 Type: schema.TypeString, 102 Optional: true, 103 ForceNew: true, 104 }, 105 "network": &schema.Schema{ 106 Type: schema.TypeList, 107 Optional: true, 108 ForceNew: true, 109 Elem: &schema.Resource{ 110 Schema: map[string]*schema.Schema{ 111 "uuid": &schema.Schema{ 112 Type: schema.TypeString, 113 Optional: true, 114 }, 115 "port": &schema.Schema{ 116 Type: schema.TypeString, 117 Optional: true, 118 }, 119 "fixed_ip": &schema.Schema{ 120 Type: schema.TypeString, 121 Optional: true, 122 }, 123 }, 124 }, 125 }, 126 "metadata": &schema.Schema{ 127 Type: schema.TypeMap, 128 Optional: true, 129 ForceNew: false, 130 }, 131 "config_drive": &schema.Schema{ 132 Type: schema.TypeBool, 133 Optional: true, 134 ForceNew: true, 135 }, 136 "admin_pass": &schema.Schema{ 137 Type: schema.TypeString, 138 Optional: true, 139 ForceNew: false, 140 }, 141 "access_ip_v4": &schema.Schema{ 142 Type: schema.TypeString, 143 Computed: true, 144 Optional: true, 145 ForceNew: false, 146 }, 147 "access_ip_v6": &schema.Schema{ 148 Type: schema.TypeString, 149 Computed: true, 150 Optional: true, 151 ForceNew: false, 152 }, 153 "key_pair": &schema.Schema{ 154 Type: schema.TypeString, 155 Optional: true, 156 ForceNew: true, 157 }, 158 "block_device": &schema.Schema{ 159 Type: schema.TypeList, 160 Optional: true, 161 ForceNew: true, 162 Elem: &schema.Resource{ 163 Schema: map[string]*schema.Schema{ 164 "uuid": &schema.Schema{ 165 Type: schema.TypeString, 166 Required: true, 167 }, 168 "source_type": &schema.Schema{ 169 Type: schema.TypeString, 170 Required: true, 171 }, 172 "volume_size": &schema.Schema{ 173 Type: schema.TypeInt, 174 Optional: true, 175 }, 176 "destination_type": &schema.Schema{ 177 Type: schema.TypeString, 178 Optional: true, 179 }, 180 "boot_index": &schema.Schema{ 181 Type: schema.TypeInt, 182 Optional: true, 183 }, 184 }, 185 }, 186 }, 187 "volume": &schema.Schema{ 188 Type: schema.TypeSet, 189 Optional: true, 190 Elem: &schema.Resource{ 191 Schema: map[string]*schema.Schema{ 192 "id": &schema.Schema{ 193 Type: schema.TypeString, 194 Optional: true, 195 Computed: true, 196 }, 197 "volume_id": &schema.Schema{ 198 Type: schema.TypeString, 199 Required: true, 200 }, 201 "device": &schema.Schema{ 202 Type: schema.TypeString, 203 Optional: true, 204 Computed: true, 205 }, 206 }, 207 }, 208 Set: resourceComputeVolumeAttachmentHash, 209 }, 210 }, 211 } 212 } 213 214 func resourceComputeInstanceV2Create(d *schema.ResourceData, meta interface{}) error { 215 config := meta.(*Config) 216 computeClient, err := config.computeV2Client(d.Get("region").(string)) 217 if err != nil { 218 return fmt.Errorf("Error creating OpenStack compute client: %s", err) 219 } 220 221 var createOpts servers.CreateOptsBuilder 222 223 imageId, err := getImageID(computeClient, d) 224 if err != nil { 225 return err 226 } 227 228 flavorId, err := getFlavorID(computeClient, d) 229 if err != nil { 230 return err 231 } 232 233 createOpts = &servers.CreateOpts{ 234 Name: d.Get("name").(string), 235 ImageRef: imageId, 236 FlavorRef: flavorId, 237 SecurityGroups: resourceInstanceSecGroupsV2(d), 238 AvailabilityZone: d.Get("availability_zone").(string), 239 Networks: resourceInstanceNetworksV2(d), 240 Metadata: resourceInstanceMetadataV2(d), 241 ConfigDrive: d.Get("config_drive").(bool), 242 AdminPass: d.Get("admin_pass").(string), 243 UserData: []byte(d.Get("user_data").(string)), 244 } 245 246 if keyName, ok := d.Get("key_pair").(string); ok && keyName != "" { 247 createOpts = &keypairs.CreateOptsExt{ 248 createOpts, 249 keyName, 250 } 251 } 252 253 if blockDeviceRaw, ok := d.Get("block_device").(map[string]interface{}); ok && blockDeviceRaw != nil { 254 blockDevice := resourceInstanceBlockDeviceV2(d, blockDeviceRaw) 255 createOpts = &bootfromvolume.CreateOptsExt{ 256 createOpts, 257 blockDevice, 258 } 259 } 260 261 log.Printf("[DEBUG] Create Options: %#v", createOpts) 262 server, err := servers.Create(computeClient, createOpts).Extract() 263 if err != nil { 264 return fmt.Errorf("Error creating OpenStack server: %s", err) 265 } 266 log.Printf("[INFO] Instance ID: %s", server.ID) 267 268 // Store the ID now 269 d.SetId(server.ID) 270 271 // Wait for the instance to become running so we can get some attributes 272 // that aren't available until later. 273 log.Printf( 274 "[DEBUG] Waiting for instance (%s) to become running", 275 server.ID) 276 277 stateConf := &resource.StateChangeConf{ 278 Pending: []string{"BUILD"}, 279 Target: "ACTIVE", 280 Refresh: ServerV2StateRefreshFunc(computeClient, server.ID), 281 Timeout: 10 * time.Minute, 282 Delay: 10 * time.Second, 283 MinTimeout: 3 * time.Second, 284 } 285 286 _, err = stateConf.WaitForState() 287 if err != nil { 288 return fmt.Errorf( 289 "Error waiting for instance (%s) to become ready: %s", 290 server.ID, err) 291 } 292 floatingIP := d.Get("floating_ip").(string) 293 if floatingIP != "" { 294 networkingClient, err := config.networkingV2Client(d.Get("region").(string)) 295 if err != nil { 296 return fmt.Errorf("Error creating OpenStack compute client: %s", err) 297 } 298 299 allFloatingIPs, err := getFloatingIPs(networkingClient) 300 if err != nil { 301 return fmt.Errorf("Error listing OpenStack floating IPs: %s", err) 302 } 303 err = assignFloatingIP(networkingClient, extractFloatingIPFromIP(allFloatingIPs, floatingIP), server.ID) 304 if err != nil { 305 return fmt.Errorf("Error assigning floating IP to OpenStack compute instance: %s", err) 306 } 307 } 308 309 // were volume attachments specified? 310 if v := d.Get("volume"); v != nil { 311 vols := v.(*schema.Set).List() 312 if len(vols) > 0 { 313 if blockClient, err := config.blockStorageV1Client(d.Get("region").(string)); err != nil { 314 return fmt.Errorf("Error creating OpenStack block storage client: %s", err) 315 } else { 316 if err := attachVolumesToInstance(computeClient, blockClient, d.Id(), vols); err != nil { 317 return err 318 } 319 } 320 } 321 } 322 323 return resourceComputeInstanceV2Read(d, meta) 324 } 325 326 func resourceComputeInstanceV2Read(d *schema.ResourceData, meta interface{}) error { 327 config := meta.(*Config) 328 computeClient, err := config.computeV2Client(d.Get("region").(string)) 329 if err != nil { 330 return fmt.Errorf("Error creating OpenStack compute client: %s", err) 331 } 332 333 server, err := servers.Get(computeClient, d.Id()).Extract() 334 if err != nil { 335 return CheckDeleted(d, err, "server") 336 } 337 338 log.Printf("[DEBUG] Retreived Server %s: %+v", d.Id(), server) 339 340 d.Set("name", server.Name) 341 d.Set("access_ip_v4", server.AccessIPv4) 342 d.Set("access_ip_v6", server.AccessIPv6) 343 344 hostv4 := server.AccessIPv4 345 if hostv4 == "" { 346 if publicAddressesRaw, ok := server.Addresses["public"]; ok { 347 publicAddresses := publicAddressesRaw.([]interface{}) 348 for _, paRaw := range publicAddresses { 349 pa := paRaw.(map[string]interface{}) 350 if pa["version"].(float64) == 4 { 351 hostv4 = pa["addr"].(string) 352 break 353 } 354 } 355 } 356 } 357 358 // If no host found, just get the first IPv4 we find 359 if hostv4 == "" { 360 for _, networkAddresses := range server.Addresses { 361 for _, element := range networkAddresses.([]interface{}) { 362 address := element.(map[string]interface{}) 363 if address["version"].(float64) == 4 { 364 hostv4 = address["addr"].(string) 365 break 366 } 367 } 368 } 369 } 370 d.Set("access_ip_v4", hostv4) 371 log.Printf("hostv4: %s", hostv4) 372 373 hostv6 := server.AccessIPv6 374 if hostv6 == "" { 375 if publicAddressesRaw, ok := server.Addresses["public"]; ok { 376 publicAddresses := publicAddressesRaw.([]interface{}) 377 for _, paRaw := range publicAddresses { 378 pa := paRaw.(map[string]interface{}) 379 if pa["version"].(float64) == 6 { 380 hostv6 = fmt.Sprintf("[%s]", pa["addr"].(string)) 381 break 382 } 383 } 384 } 385 } 386 387 // If no hostv6 found, just get the first IPv6 we find 388 if hostv6 == "" { 389 for _, networkAddresses := range server.Addresses { 390 for _, element := range networkAddresses.([]interface{}) { 391 address := element.(map[string]interface{}) 392 if address["version"].(float64) == 6 { 393 hostv6 = fmt.Sprintf("[%s]", address["addr"].(string)) 394 break 395 } 396 } 397 } 398 } 399 d.Set("access_ip_v6", hostv6) 400 log.Printf("hostv6: %s", hostv6) 401 402 preferredv := "" 403 if hostv4 != "" { 404 preferredv = hostv4 405 } else if hostv6 != "" { 406 preferredv = hostv6 407 } 408 409 if preferredv != "" { 410 // Initialize the connection info 411 d.SetConnInfo(map[string]string{ 412 "type": "ssh", 413 "host": preferredv, 414 }) 415 } 416 417 d.Set("metadata", server.Metadata) 418 419 secGrpNames := []string{} 420 for _, sg := range server.SecurityGroups { 421 secGrpNames = append(secGrpNames, sg["name"].(string)) 422 } 423 d.Set("security_groups", secGrpNames) 424 425 flavorId, ok := server.Flavor["id"].(string) 426 if !ok { 427 return fmt.Errorf("Error setting OpenStack server's flavor: %v", server.Flavor) 428 } 429 d.Set("flavor_id", flavorId) 430 431 flavor, err := flavors.Get(computeClient, flavorId).Extract() 432 if err != nil { 433 return err 434 } 435 d.Set("flavor_name", flavor.Name) 436 437 imageId, ok := server.Image["id"].(string) 438 if !ok { 439 return fmt.Errorf("Error setting OpenStack server's image: %v", server.Image) 440 } 441 d.Set("image_id", imageId) 442 443 image, err := images.Get(computeClient, imageId).Extract() 444 if err != nil { 445 return err 446 } 447 d.Set("image_name", image.Name) 448 449 // volume attachments 450 vas, err := getVolumeAttachments(computeClient, d.Id()) 451 if err != nil { 452 return err 453 } 454 if len(vas) > 0 { 455 attachments := make([]map[string]interface{}, len(vas)) 456 for i, attachment := range vas { 457 attachments[i] = make(map[string]interface{}) 458 attachments[i]["id"] = attachment.ID 459 attachments[i]["volume_id"] = attachment.VolumeID 460 attachments[i]["device"] = attachment.Device 461 } 462 log.Printf("[INFO] Volume attachments: %v", attachments) 463 d.Set("volume", attachments) 464 } 465 466 return nil 467 } 468 469 func resourceComputeInstanceV2Update(d *schema.ResourceData, meta interface{}) error { 470 config := meta.(*Config) 471 computeClient, err := config.computeV2Client(d.Get("region").(string)) 472 if err != nil { 473 return fmt.Errorf("Error creating OpenStack compute client: %s", err) 474 } 475 476 var updateOpts servers.UpdateOpts 477 if d.HasChange("name") { 478 updateOpts.Name = d.Get("name").(string) 479 } 480 if d.HasChange("access_ip_v4") { 481 updateOpts.AccessIPv4 = d.Get("access_ip_v4").(string) 482 } 483 if d.HasChange("access_ip_v6") { 484 updateOpts.AccessIPv4 = d.Get("access_ip_v6").(string) 485 } 486 487 if updateOpts != (servers.UpdateOpts{}) { 488 _, err := servers.Update(computeClient, d.Id(), updateOpts).Extract() 489 if err != nil { 490 return fmt.Errorf("Error updating OpenStack server: %s", err) 491 } 492 } 493 494 if d.HasChange("metadata") { 495 var metadataOpts servers.MetadataOpts 496 metadataOpts = make(servers.MetadataOpts) 497 newMetadata := d.Get("metadata").(map[string]interface{}) 498 for k, v := range newMetadata { 499 metadataOpts[k] = v.(string) 500 } 501 502 _, err := servers.UpdateMetadata(computeClient, d.Id(), metadataOpts).Extract() 503 if err != nil { 504 return fmt.Errorf("Error updating OpenStack server (%s) metadata: %s", d.Id(), err) 505 } 506 } 507 508 if d.HasChange("security_groups") { 509 oldSGRaw, newSGRaw := d.GetChange("security_groups") 510 oldSGSlice, newSGSlice := oldSGRaw.([]interface{}), newSGRaw.([]interface{}) 511 oldSGSet := schema.NewSet(func(v interface{}) int { return hashcode.String(v.(string)) }, oldSGSlice) 512 newSGSet := schema.NewSet(func(v interface{}) int { return hashcode.String(v.(string)) }, newSGSlice) 513 secgroupsToAdd := newSGSet.Difference(oldSGSet) 514 secgroupsToRemove := oldSGSet.Difference(newSGSet) 515 516 log.Printf("[DEBUG] Security groups to add: %v", secgroupsToAdd) 517 518 log.Printf("[DEBUG] Security groups to remove: %v", secgroupsToRemove) 519 520 for _, g := range secgroupsToAdd.List() { 521 err := secgroups.AddServerToGroup(computeClient, d.Id(), g.(string)).ExtractErr() 522 if err != nil { 523 return fmt.Errorf("Error adding security group to OpenStack server (%s): %s", d.Id(), err) 524 } 525 log.Printf("[DEBUG] Added security group (%s) to instance (%s)", g.(string), d.Id()) 526 } 527 528 for _, g := range secgroupsToRemove.List() { 529 err := secgroups.RemoveServerFromGroup(computeClient, d.Id(), g.(string)).ExtractErr() 530 if err != nil { 531 errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) 532 if !ok { 533 return fmt.Errorf("Error removing security group from OpenStack server (%s): %s", d.Id(), err) 534 } 535 if errCode.Actual == 404 { 536 continue 537 } else { 538 return fmt.Errorf("Error removing security group from OpenStack server (%s): %s", d.Id(), err) 539 } 540 } else { 541 log.Printf("[DEBUG] Removed security group (%s) from instance (%s)", g.(string), d.Id()) 542 } 543 } 544 } 545 546 if d.HasChange("admin_pass") { 547 if newPwd, ok := d.Get("admin_pass").(string); ok { 548 err := servers.ChangeAdminPassword(computeClient, d.Id(), newPwd).ExtractErr() 549 if err != nil { 550 return fmt.Errorf("Error changing admin password of OpenStack server (%s): %s", d.Id(), err) 551 } 552 } 553 } 554 555 if d.HasChange("floating_ip") { 556 floatingIP := d.Get("floating_ip").(string) 557 if floatingIP != "" { 558 networkingClient, err := config.networkingV2Client(d.Get("region").(string)) 559 if err != nil { 560 return fmt.Errorf("Error creating OpenStack compute client: %s", err) 561 } 562 563 allFloatingIPs, err := getFloatingIPs(networkingClient) 564 if err != nil { 565 return fmt.Errorf("Error listing OpenStack floating IPs: %s", err) 566 } 567 err = assignFloatingIP(networkingClient, extractFloatingIPFromIP(allFloatingIPs, floatingIP), d.Id()) 568 if err != nil { 569 fmt.Errorf("Error assigning floating IP to OpenStack compute instance: %s", err) 570 } 571 } 572 } 573 574 if d.HasChange("volume") { 575 // old attachments and new attachments 576 oldAttachments, newAttachments := d.GetChange("volume") 577 578 // for each old attachment, detach the volume 579 oldAttachmentSet := oldAttachments.(*schema.Set).List() 580 if len(oldAttachmentSet) > 0 { 581 if blockClient, err := config.blockStorageV1Client(d.Get("region").(string)); err != nil { 582 return err 583 } else { 584 if err := detachVolumesFromInstance(computeClient, blockClient, d.Id(), oldAttachmentSet); err != nil { 585 return err 586 } 587 } 588 } 589 590 // for each new attachment, attach the volume 591 newAttachmentSet := newAttachments.(*schema.Set).List() 592 if len(newAttachmentSet) > 0 { 593 if blockClient, err := config.blockStorageV1Client(d.Get("region").(string)); err != nil { 594 return err 595 } else { 596 if err := attachVolumesToInstance(computeClient, blockClient, d.Id(), newAttachmentSet); err != nil { 597 return err 598 } 599 } 600 } 601 602 d.SetPartial("volume") 603 } 604 605 if d.HasChange("flavor_id") || d.HasChange("flavor_name") { 606 flavorId, err := getFlavorID(computeClient, d) 607 if err != nil { 608 return err 609 } 610 resizeOpts := &servers.ResizeOpts{ 611 FlavorRef: flavorId, 612 } 613 log.Printf("[DEBUG] Resize configuration: %#v", resizeOpts) 614 err = servers.Resize(computeClient, d.Id(), resizeOpts).ExtractErr() 615 if err != nil { 616 return fmt.Errorf("Error resizing OpenStack server: %s", err) 617 } 618 619 // Wait for the instance to finish resizing. 620 log.Printf("[DEBUG] Waiting for instance (%s) to finish resizing", d.Id()) 621 622 stateConf := &resource.StateChangeConf{ 623 Pending: []string{"RESIZE"}, 624 Target: "VERIFY_RESIZE", 625 Refresh: ServerV2StateRefreshFunc(computeClient, d.Id()), 626 Timeout: 3 * time.Minute, 627 Delay: 10 * time.Second, 628 MinTimeout: 3 * time.Second, 629 } 630 631 _, err = stateConf.WaitForState() 632 if err != nil { 633 return fmt.Errorf("Error waiting for instance (%s) to resize: %s", d.Id(), err) 634 } 635 636 // Confirm resize. 637 log.Printf("[DEBUG] Confirming resize") 638 err = servers.ConfirmResize(computeClient, d.Id()).ExtractErr() 639 if err != nil { 640 return fmt.Errorf("Error confirming resize of OpenStack server: %s", err) 641 } 642 643 stateConf = &resource.StateChangeConf{ 644 Pending: []string{"VERIFY_RESIZE"}, 645 Target: "ACTIVE", 646 Refresh: ServerV2StateRefreshFunc(computeClient, d.Id()), 647 Timeout: 3 * time.Minute, 648 Delay: 10 * time.Second, 649 MinTimeout: 3 * time.Second, 650 } 651 652 _, err = stateConf.WaitForState() 653 if err != nil { 654 return fmt.Errorf("Error waiting for instance (%s) to confirm resize: %s", d.Id(), err) 655 } 656 } 657 658 return resourceComputeInstanceV2Read(d, meta) 659 } 660 661 func resourceComputeInstanceV2Delete(d *schema.ResourceData, meta interface{}) error { 662 config := meta.(*Config) 663 computeClient, err := config.computeV2Client(d.Get("region").(string)) 664 if err != nil { 665 return fmt.Errorf("Error creating OpenStack compute client: %s", err) 666 } 667 668 err = servers.Delete(computeClient, d.Id()).ExtractErr() 669 if err != nil { 670 return fmt.Errorf("Error deleting OpenStack server: %s", err) 671 } 672 673 // Wait for the instance to delete before moving on. 674 log.Printf("[DEBUG] Waiting for instance (%s) to delete", d.Id()) 675 676 stateConf := &resource.StateChangeConf{ 677 Pending: []string{"ACTIVE"}, 678 Target: "DELETED", 679 Refresh: ServerV2StateRefreshFunc(computeClient, d.Id()), 680 Timeout: 10 * time.Minute, 681 Delay: 10 * time.Second, 682 MinTimeout: 3 * time.Second, 683 } 684 685 _, err = stateConf.WaitForState() 686 if err != nil { 687 return fmt.Errorf( 688 "Error waiting for instance (%s) to delete: %s", 689 d.Id(), err) 690 } 691 692 d.SetId("") 693 return nil 694 } 695 696 // ServerV2StateRefreshFunc returns a resource.StateRefreshFunc that is used to watch 697 // an OpenStack instance. 698 func ServerV2StateRefreshFunc(client *gophercloud.ServiceClient, instanceID string) resource.StateRefreshFunc { 699 return func() (interface{}, string, error) { 700 s, err := servers.Get(client, instanceID).Extract() 701 if err != nil { 702 errCode, ok := err.(*gophercloud.UnexpectedResponseCodeError) 703 if !ok { 704 return nil, "", err 705 } 706 if errCode.Actual == 404 { 707 return s, "DELETED", nil 708 } 709 return nil, "", err 710 } 711 712 return s, s.Status, nil 713 } 714 } 715 716 func resourceInstanceSecGroupsV2(d *schema.ResourceData) []string { 717 rawSecGroups := d.Get("security_groups").([]interface{}) 718 secgroups := make([]string, len(rawSecGroups)) 719 for i, raw := range rawSecGroups { 720 secgroups[i] = raw.(string) 721 } 722 return secgroups 723 } 724 725 func resourceInstanceNetworksV2(d *schema.ResourceData) []servers.Network { 726 rawNetworks := d.Get("network").([]interface{}) 727 networks := make([]servers.Network, len(rawNetworks)) 728 for i, raw := range rawNetworks { 729 rawMap := raw.(map[string]interface{}) 730 networks[i] = servers.Network{ 731 UUID: rawMap["uuid"].(string), 732 Port: rawMap["port"].(string), 733 FixedIP: rawMap["fixed_ip"].(string), 734 } 735 } 736 return networks 737 } 738 739 func resourceInstanceMetadataV2(d *schema.ResourceData) map[string]string { 740 m := make(map[string]string) 741 for key, val := range d.Get("metadata").(map[string]interface{}) { 742 m[key] = val.(string) 743 } 744 return m 745 } 746 747 func resourceInstanceBlockDeviceV2(d *schema.ResourceData, bd map[string]interface{}) []bootfromvolume.BlockDevice { 748 sourceType := bootfromvolume.SourceType(bd["source_type"].(string)) 749 bfvOpts := []bootfromvolume.BlockDevice{ 750 bootfromvolume.BlockDevice{ 751 UUID: bd["uuid"].(string), 752 SourceType: sourceType, 753 VolumeSize: bd["volume_size"].(int), 754 DestinationType: bd["destination_type"].(string), 755 BootIndex: bd["boot_index"].(int), 756 }, 757 } 758 759 return bfvOpts 760 } 761 762 func extractFloatingIPFromIP(ips []floatingips.FloatingIP, IP string) *floatingips.FloatingIP { 763 for _, floatingIP := range ips { 764 if floatingIP.FloatingIP == IP { 765 return &floatingIP 766 } 767 } 768 return nil 769 } 770 771 func assignFloatingIP(networkingClient *gophercloud.ServiceClient, floatingIP *floatingips.FloatingIP, instanceID string) error { 772 portID, err := getInstancePortID(networkingClient, instanceID) 773 if err != nil { 774 return err 775 } 776 return floatingips.Update(networkingClient, floatingIP.ID, floatingips.UpdateOpts{ 777 PortID: portID, 778 }).Err 779 } 780 781 func getInstancePortID(networkingClient *gophercloud.ServiceClient, instanceID string) (string, error) { 782 pager := ports.List(networkingClient, ports.ListOpts{ 783 DeviceID: instanceID, 784 }) 785 786 var portID string 787 err := pager.EachPage(func(page pagination.Page) (bool, error) { 788 portList, err := ports.ExtractPorts(page) 789 if err != nil { 790 return false, err 791 } 792 for _, port := range portList { 793 portID = port.ID 794 return false, nil 795 } 796 return true, nil 797 }) 798 799 if err != nil { 800 return "", err 801 } 802 803 if portID == "" { 804 return "", fmt.Errorf("Cannot find port for instance %s", instanceID) 805 } 806 807 return portID, nil 808 } 809 810 func getFloatingIPs(networkingClient *gophercloud.ServiceClient) ([]floatingips.FloatingIP, error) { 811 pager := floatingips.List(networkingClient, floatingips.ListOpts{}) 812 813 ips := []floatingips.FloatingIP{} 814 err := pager.EachPage(func(page pagination.Page) (bool, error) { 815 floatingipList, err := floatingips.ExtractFloatingIPs(page) 816 if err != nil { 817 return false, err 818 } 819 for _, f := range floatingipList { 820 ips = append(ips, f) 821 } 822 return true, nil 823 }) 824 825 if err != nil { 826 return nil, err 827 } 828 return ips, nil 829 } 830 831 func getImageID(client *gophercloud.ServiceClient, d *schema.ResourceData) (string, error) { 832 imageId := d.Get("image_id").(string) 833 834 if imageId != "" { 835 return imageId, nil 836 } 837 838 imageCount := 0 839 imageName := d.Get("image_name").(string) 840 if imageName != "" { 841 pager := images.ListDetail(client, &images.ListOpts{ 842 Name: imageName, 843 }) 844 pager.EachPage(func(page pagination.Page) (bool, error) { 845 imageList, err := images.ExtractImages(page) 846 if err != nil { 847 return false, err 848 } 849 850 for _, i := range imageList { 851 if i.Name == imageName { 852 imageCount++ 853 imageId = i.ID 854 } 855 } 856 return true, nil 857 }) 858 859 switch imageCount { 860 case 0: 861 return "", fmt.Errorf("Unable to find image: %s", imageName) 862 case 1: 863 return imageId, nil 864 default: 865 return "", fmt.Errorf("Found %d images matching %s", imageCount, imageName) 866 } 867 } 868 return "", fmt.Errorf("Neither an image ID nor an image name were able to be determined.") 869 } 870 871 func getFlavorID(client *gophercloud.ServiceClient, d *schema.ResourceData) (string, error) { 872 flavorId := d.Get("flavor_id").(string) 873 874 if flavorId != "" { 875 return flavorId, nil 876 } 877 878 flavorCount := 0 879 flavorName := d.Get("flavor_name").(string) 880 if flavorName != "" { 881 pager := flavors.ListDetail(client, nil) 882 pager.EachPage(func(page pagination.Page) (bool, error) { 883 flavorList, err := flavors.ExtractFlavors(page) 884 if err != nil { 885 return false, err 886 } 887 888 for _, f := range flavorList { 889 if f.Name == flavorName { 890 flavorCount++ 891 flavorId = f.ID 892 } 893 } 894 return true, nil 895 }) 896 897 switch flavorCount { 898 case 0: 899 return "", fmt.Errorf("Unable to find flavor: %s", flavorName) 900 case 1: 901 return flavorId, nil 902 default: 903 return "", fmt.Errorf("Found %d flavors matching %s", flavorCount, flavorName) 904 } 905 } 906 return "", fmt.Errorf("Neither a flavor ID nor a flavor name were able to be determined.") 907 } 908 909 func resourceComputeVolumeAttachmentHash(v interface{}) int { 910 var buf bytes.Buffer 911 m := v.(map[string]interface{}) 912 buf.WriteString(fmt.Sprintf("%s-", m["volume_id"].(string))) 913 buf.WriteString(fmt.Sprintf("%s-", m["device"].(string))) 914 return hashcode.String(buf.String()) 915 } 916 917 func attachVolumesToInstance(computeClient *gophercloud.ServiceClient, blockClient *gophercloud.ServiceClient, serverId string, vols []interface{}) error { 918 if len(vols) > 0 { 919 for _, v := range vols { 920 va := v.(map[string]interface{}) 921 volumeId := va["volume_id"].(string) 922 device := va["device"].(string) 923 924 s := "" 925 if serverId != "" { 926 s = serverId 927 } else if va["server_id"] != "" { 928 s = va["server_id"].(string) 929 } else { 930 return fmt.Errorf("Unable to determine server ID to attach volume.") 931 } 932 933 vaOpts := &volumeattach.CreateOpts{ 934 Device: device, 935 VolumeID: volumeId, 936 } 937 938 if _, err := volumeattach.Create(computeClient, s, vaOpts).Extract(); err != nil { 939 return err 940 } 941 942 stateConf := &resource.StateChangeConf{ 943 Pending: []string{"attaching", "available"}, 944 Target: "in-use", 945 Refresh: VolumeV1StateRefreshFunc(blockClient, va["volume_id"].(string)), 946 Timeout: 30 * time.Minute, 947 Delay: 5 * time.Second, 948 MinTimeout: 2 * time.Second, 949 } 950 951 if _, err := stateConf.WaitForState(); err != nil { 952 return err 953 } 954 955 log.Printf("[INFO] Attached volume %s to instance %s", volumeId, serverId) 956 } 957 } 958 return nil 959 } 960 961 func detachVolumesFromInstance(computeClient *gophercloud.ServiceClient, blockClient *gophercloud.ServiceClient, serverId string, vols []interface{}) error { 962 if len(vols) > 0 { 963 for _, v := range vols { 964 va := v.(map[string]interface{}) 965 aId := va["id"].(string) 966 967 if err := volumeattach.Delete(computeClient, serverId, aId).ExtractErr(); err != nil { 968 return err 969 } 970 971 stateConf := &resource.StateChangeConf{ 972 Pending: []string{"detaching", "in-use"}, 973 Target: "available", 974 Refresh: VolumeV1StateRefreshFunc(blockClient, va["volume_id"].(string)), 975 Timeout: 30 * time.Minute, 976 Delay: 5 * time.Second, 977 MinTimeout: 2 * time.Second, 978 } 979 980 if _, err := stateConf.WaitForState(); err != nil { 981 return err 982 } 983 log.Printf("[INFO] Detached volume %s from instance %s", va["volume_id"], serverId) 984 } 985 } 986 987 return nil 988 } 989 990 func getVolumeAttachments(computeClient *gophercloud.ServiceClient, serverId string) ([]volumeattach.VolumeAttachment, error) { 991 var attachments []volumeattach.VolumeAttachment 992 err := volumeattach.List(computeClient, serverId).EachPage(func(page pagination.Page) (bool, error) { 993 actual, err := volumeattach.ExtractVolumeAttachments(page) 994 if err != nil { 995 return false, err 996 } 997 998 attachments = actual 999 return true, nil 1000 }) 1001 1002 if err != nil { 1003 return nil, err 1004 } 1005 1006 return attachments, nil 1007 }