github.com/gophercloud/gophercloud@v1.11.0/internal/acceptance/openstack/compute/v2/compute.go (about) 1 // Package v2 contains common functions for creating compute-based resources 2 // for use in acceptance tests. See the `*_test.go` files for example usages. 3 package v2 4 5 import ( 6 "crypto/rand" 7 "crypto/rsa" 8 "fmt" 9 "testing" 10 11 "github.com/gophercloud/gophercloud" 12 "github.com/gophercloud/gophercloud/internal/acceptance/clients" 13 "github.com/gophercloud/gophercloud/internal/acceptance/tools" 14 "github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes" 15 "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/aggregates" 16 "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces" 17 "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/bootfromvolume" 18 dsr "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/defsecrules" 19 "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips" 20 "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/keypairs" 21 "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/networks" 22 "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/quotasets" 23 "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/remoteconsoles" 24 "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/rescueunrescue" 25 "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/schedulerhints" 26 "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/secgroups" 27 "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/servergroups" 28 "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/tenantnetworks" 29 "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach" 30 "github.com/gophercloud/gophercloud/openstack/compute/v2/flavors" 31 "github.com/gophercloud/gophercloud/openstack/compute/v2/servers" 32 neutron "github.com/gophercloud/gophercloud/openstack/networking/v2/networks" 33 th "github.com/gophercloud/gophercloud/testhelper" 34 35 "golang.org/x/crypto/ssh" 36 ) 37 38 // AssociateFloatingIP will associate a floating IP with an instance. An error 39 // will be returned if the floating IP was unable to be associated. 40 func AssociateFloatingIP(t *testing.T, client *gophercloud.ServiceClient, floatingIP *floatingips.FloatingIP, server *servers.Server) error { 41 associateOpts := floatingips.AssociateOpts{ 42 FloatingIP: floatingIP.IP, 43 } 44 45 t.Logf("Attempting to associate floating IP %s to instance %s", floatingIP.IP, server.ID) 46 err := floatingips.AssociateInstance(client, server.ID, associateOpts).ExtractErr() 47 if err != nil { 48 return err 49 } 50 51 return nil 52 } 53 54 // AssociateFloatingIPWithFixedIP will associate a floating IP with an 55 // instance's specific fixed IP. An error will be returend if the floating IP 56 // was unable to be associated. 57 func AssociateFloatingIPWithFixedIP(t *testing.T, client *gophercloud.ServiceClient, floatingIP *floatingips.FloatingIP, server *servers.Server, fixedIP string) error { 58 associateOpts := floatingips.AssociateOpts{ 59 FloatingIP: floatingIP.IP, 60 FixedIP: fixedIP, 61 } 62 63 t.Logf("Attempting to associate floating IP %s to fixed IP %s on instance %s", floatingIP.IP, fixedIP, server.ID) 64 err := floatingips.AssociateInstance(client, server.ID, associateOpts).ExtractErr() 65 if err != nil { 66 return err 67 } 68 69 return nil 70 } 71 72 // AttachInterface will create and attach an interface on a given server. 73 // An error will returned if the interface could not be created. 74 func AttachInterface(t *testing.T, client *gophercloud.ServiceClient, serverID string) (*attachinterfaces.Interface, error) { 75 t.Logf("Attempting to attach interface to server %s", serverID) 76 77 choices, err := clients.AcceptanceTestChoicesFromEnv() 78 if err != nil { 79 t.Fatal(err) 80 } 81 82 networkID, err := GetNetworkIDFromNetworks(t, client, choices.NetworkName) 83 if err != nil { 84 return nil, err 85 } 86 87 createOpts := attachinterfaces.CreateOpts{ 88 NetworkID: networkID, 89 } 90 91 iface, err := attachinterfaces.Create(client, serverID, createOpts).Extract() 92 if err != nil { 93 return nil, err 94 } 95 96 t.Logf("Successfully created interface %s on server %s", iface.PortID, serverID) 97 98 return iface, nil 99 } 100 101 // CreateAggregate will create an aggregate with random name and available zone. 102 // An error will be returned if the aggregate could not be created. 103 func CreateAggregate(t *testing.T, client *gophercloud.ServiceClient) (*aggregates.Aggregate, error) { 104 aggregateName := tools.RandomString("aggregate_", 5) 105 availabilityZone := tools.RandomString("zone_", 5) 106 t.Logf("Attempting to create aggregate %s", aggregateName) 107 108 createOpts := aggregates.CreateOpts{ 109 Name: aggregateName, 110 AvailabilityZone: availabilityZone, 111 } 112 113 aggregate, err := aggregates.Create(client, createOpts).Extract() 114 if err != nil { 115 return nil, err 116 } 117 118 t.Logf("Successfully created aggregate %d", aggregate.ID) 119 120 aggregate, err = aggregates.Get(client, aggregate.ID).Extract() 121 if err != nil { 122 return nil, err 123 } 124 125 th.AssertEquals(t, aggregate.Name, aggregateName) 126 th.AssertEquals(t, aggregate.AvailabilityZone, availabilityZone) 127 128 return aggregate, nil 129 } 130 131 // CreateBootableVolumeServer works like CreateServer but is configured with 132 // one or more block devices defined by passing in []bootfromvolume.BlockDevice. 133 // An error will be returned if a server was unable to be created. 134 func CreateBootableVolumeServer(t *testing.T, client *gophercloud.ServiceClient, blockDevices []bootfromvolume.BlockDevice) (*servers.Server, error) { 135 var server *servers.Server 136 137 choices, err := clients.AcceptanceTestChoicesFromEnv() 138 if err != nil { 139 t.Fatal(err) 140 } 141 142 networkID, err := GetNetworkIDFromNetworks(t, client, choices.NetworkName) 143 if err != nil { 144 return server, err 145 } 146 147 name := tools.RandomString("ACPTTEST", 16) 148 t.Logf("Attempting to create bootable volume server: %s", name) 149 150 serverCreateOpts := servers.CreateOpts{ 151 Name: name, 152 FlavorRef: choices.FlavorID, 153 Networks: []servers.Network{ 154 {UUID: networkID}, 155 }, 156 } 157 158 if blockDevices[0].SourceType == bootfromvolume.SourceImage && blockDevices[0].DestinationType == bootfromvolume.DestinationLocal { 159 serverCreateOpts.ImageRef = blockDevices[0].UUID 160 } 161 162 server, err = bootfromvolume.Create(client, bootfromvolume.CreateOptsExt{ 163 CreateOptsBuilder: serverCreateOpts, 164 BlockDevice: blockDevices, 165 }).Extract() 166 167 if err != nil { 168 return server, err 169 } 170 171 if err := WaitForComputeStatus(client, server, "ACTIVE"); err != nil { 172 return server, err 173 } 174 175 newServer, err := servers.Get(client, server.ID).Extract() 176 if err != nil { 177 return nil, err 178 } 179 180 th.AssertEquals(t, newServer.Name, name) 181 182 return newServer, nil 183 } 184 185 // CreateDefaultRule will create a default security group rule with a 186 // random port range between 80 and 90. An error will be returned if 187 // a default rule was unable to be created. 188 func CreateDefaultRule(t *testing.T, client *gophercloud.ServiceClient) (dsr.DefaultRule, error) { 189 createOpts := dsr.CreateOpts{ 190 FromPort: tools.RandomInt(80, 89), 191 ToPort: tools.RandomInt(90, 99), 192 IPProtocol: "TCP", 193 CIDR: "0.0.0.0/0", 194 } 195 196 defaultRule, err := dsr.Create(client, createOpts).Extract() 197 if err != nil { 198 return *defaultRule, err 199 } 200 201 t.Logf("Created default rule: %s", defaultRule.ID) 202 203 return *defaultRule, nil 204 } 205 206 // CreateFlavor will create a flavor with a random name. 207 // An error will be returned if the flavor could not be created. 208 func CreateFlavor(t *testing.T, client *gophercloud.ServiceClient) (*flavors.Flavor, error) { 209 flavorName := tools.RandomString("flavor_", 5) 210 flavorDescription := fmt.Sprintf("I am %s and i am a yummy flavor", flavorName) 211 212 // Microversion 2.55 is required to add description to flavor 213 client.Microversion = "2.55" 214 t.Logf("Attempting to create flavor %s", flavorName) 215 216 isPublic := true 217 createOpts := flavors.CreateOpts{ 218 Name: flavorName, 219 RAM: 1, 220 VCPUs: 1, 221 Disk: gophercloud.IntToPointer(1), 222 IsPublic: &isPublic, 223 Description: flavorDescription, 224 } 225 226 flavor, err := flavors.Create(client, createOpts).Extract() 227 if err != nil { 228 return nil, err 229 } 230 231 t.Logf("Successfully created flavor %s", flavor.ID) 232 233 th.AssertEquals(t, flavor.Name, flavorName) 234 th.AssertEquals(t, flavor.RAM, 1) 235 th.AssertEquals(t, flavor.Disk, 1) 236 th.AssertEquals(t, flavor.VCPUs, 1) 237 th.AssertEquals(t, flavor.IsPublic, true) 238 th.AssertEquals(t, flavor.Description, flavorDescription) 239 240 return flavor, nil 241 } 242 243 // CreateFloatingIP will allocate a floating IP. 244 // An error will be returend if one was unable to be allocated. 245 func CreateFloatingIP(t *testing.T, client *gophercloud.ServiceClient) (*floatingips.FloatingIP, error) { 246 choices, err := clients.AcceptanceTestChoicesFromEnv() 247 if err != nil { 248 t.Fatal(err) 249 } 250 251 createOpts := floatingips.CreateOpts{ 252 Pool: choices.FloatingIPPoolName, 253 } 254 floatingIP, err := floatingips.Create(client, createOpts).Extract() 255 if err != nil { 256 return floatingIP, err 257 } 258 259 t.Logf("Created floating IP: %s", floatingIP.ID) 260 return floatingIP, nil 261 } 262 263 func createKey() (string, error) { 264 privateKey, err := rsa.GenerateKey(rand.Reader, 2048) 265 if err != nil { 266 return "", err 267 } 268 269 publicKey := privateKey.PublicKey 270 pub, err := ssh.NewPublicKey(&publicKey) 271 if err != nil { 272 return "", err 273 } 274 275 pubBytes := ssh.MarshalAuthorizedKey(pub) 276 pk := string(pubBytes) 277 return pk, nil 278 } 279 280 // CreateKeyPair will create a KeyPair with a random name. An error will occur 281 // if the keypair failed to be created. An error will be returned if the 282 // keypair was unable to be created. 283 func CreateKeyPair(t *testing.T, client *gophercloud.ServiceClient) (*keypairs.KeyPair, error) { 284 keyPairName := tools.RandomString("keypair_", 5) 285 286 t.Logf("Attempting to create keypair: %s", keyPairName) 287 createOpts := keypairs.CreateOpts{ 288 Name: keyPairName, 289 } 290 keyPair, err := keypairs.Create(client, createOpts).Extract() 291 if err != nil { 292 return keyPair, err 293 } 294 295 t.Logf("Created keypair: %s", keyPairName) 296 297 th.AssertEquals(t, keyPair.Name, keyPairName) 298 299 return keyPair, nil 300 } 301 302 // CreateMultiEphemeralServer works like CreateServer but is configured with 303 // one or more block devices defined by passing in []bootfromvolume.BlockDevice. 304 // These block devices act like block devices when booting from a volume but 305 // are actually local ephemeral disks. 306 // An error will be returned if a server was unable to be created. 307 func CreateMultiEphemeralServer(t *testing.T, client *gophercloud.ServiceClient, blockDevices []bootfromvolume.BlockDevice) (*servers.Server, error) { 308 var server *servers.Server 309 310 choices, err := clients.AcceptanceTestChoicesFromEnv() 311 if err != nil { 312 t.Fatal(err) 313 } 314 315 networkID, err := GetNetworkIDFromNetworks(t, client, choices.NetworkName) 316 if err != nil { 317 return server, err 318 } 319 320 name := tools.RandomString("ACPTTEST", 16) 321 t.Logf("Attempting to create bootable volume server: %s", name) 322 323 serverCreateOpts := servers.CreateOpts{ 324 Name: name, 325 FlavorRef: choices.FlavorID, 326 ImageRef: choices.ImageID, 327 Networks: []servers.Network{ 328 {UUID: networkID}, 329 }, 330 } 331 332 server, err = bootfromvolume.Create(client, bootfromvolume.CreateOptsExt{ 333 CreateOptsBuilder: serverCreateOpts, 334 BlockDevice: blockDevices, 335 }).Extract() 336 337 if err != nil { 338 return server, err 339 } 340 341 if err := WaitForComputeStatus(client, server, "ACTIVE"); err != nil { 342 return server, err 343 } 344 345 newServer, err := servers.Get(client, server.ID).Extract() 346 if err != nil { 347 return server, err 348 } 349 th.AssertEquals(t, newServer.Name, name) 350 th.AssertEquals(t, newServer.Flavor["id"], choices.FlavorID) 351 th.AssertEquals(t, newServer.Image["id"], choices.ImageID) 352 353 return newServer, nil 354 } 355 356 // CreatePrivateFlavor will create a private flavor with a random name. 357 // An error will be returned if the flavor could not be created. 358 func CreatePrivateFlavor(t *testing.T, client *gophercloud.ServiceClient) (*flavors.Flavor, error) { 359 flavorName := tools.RandomString("flavor_", 5) 360 t.Logf("Attempting to create flavor %s", flavorName) 361 362 isPublic := false 363 createOpts := flavors.CreateOpts{ 364 Name: flavorName, 365 RAM: 1, 366 VCPUs: 1, 367 Disk: gophercloud.IntToPointer(1), 368 IsPublic: &isPublic, 369 } 370 371 flavor, err := flavors.Create(client, createOpts).Extract() 372 if err != nil { 373 return nil, err 374 } 375 376 t.Logf("Successfully created flavor %s", flavor.ID) 377 378 th.AssertEquals(t, flavor.Name, flavorName) 379 th.AssertEquals(t, flavor.RAM, 1) 380 th.AssertEquals(t, flavor.Disk, 1) 381 th.AssertEquals(t, flavor.VCPUs, 1) 382 th.AssertEquals(t, flavor.IsPublic, false) 383 384 return flavor, nil 385 } 386 387 // CreateSecurityGroup will create a security group with a random name. 388 // An error will be returned if one was failed to be created. 389 func CreateSecurityGroup(t *testing.T, client *gophercloud.ServiceClient) (*secgroups.SecurityGroup, error) { 390 name := tools.RandomString("secgroup_", 5) 391 392 createOpts := secgroups.CreateOpts{ 393 Name: name, 394 Description: "something", 395 } 396 397 securityGroup, err := secgroups.Create(client, createOpts).Extract() 398 if err != nil { 399 return nil, err 400 } 401 402 t.Logf("Created security group: %s", securityGroup.ID) 403 404 th.AssertEquals(t, securityGroup.Name, name) 405 406 return securityGroup, nil 407 } 408 409 // CreateSecurityGroupRule will create a security group rule with a random name 410 // and a random TCP port range between port 80 and 99. An error will be 411 // returned if the rule failed to be created. 412 func CreateSecurityGroupRule(t *testing.T, client *gophercloud.ServiceClient, securityGroupID string) (*secgroups.Rule, error) { 413 fromPort := tools.RandomInt(80, 89) 414 toPort := tools.RandomInt(90, 99) 415 createOpts := secgroups.CreateRuleOpts{ 416 ParentGroupID: securityGroupID, 417 FromPort: fromPort, 418 ToPort: toPort, 419 IPProtocol: "TCP", 420 CIDR: "0.0.0.0/0", 421 } 422 423 rule, err := secgroups.CreateRule(client, createOpts).Extract() 424 if err != nil { 425 return nil, err 426 } 427 428 t.Logf("Created security group rule: %s", rule.ID) 429 430 th.AssertEquals(t, rule.FromPort, fromPort) 431 th.AssertEquals(t, rule.ToPort, toPort) 432 th.AssertEquals(t, rule.ParentGroupID, securityGroupID) 433 434 return rule, nil 435 } 436 437 // CreateServer creates a basic instance with a randomly generated name. 438 // The flavor of the instance will be the value of the OS_FLAVOR_ID environment variable. 439 // The image will be the value of the OS_IMAGE_ID environment variable. 440 // The instance will be launched on the network specified in OS_NETWORK_NAME. 441 // An error will be returned if the instance was unable to be created. 442 func CreateServer(t *testing.T, client *gophercloud.ServiceClient) (*servers.Server, error) { 443 choices, err := clients.AcceptanceTestChoicesFromEnv() 444 if err != nil { 445 t.Fatal(err) 446 } 447 448 networkID, err := GetNetworkIDFromNetworks(t, client, choices.NetworkName) 449 if err != nil { 450 return nil, err 451 } 452 453 name := tools.RandomString("ACPTTEST", 16) 454 t.Logf("Attempting to create server: %s", name) 455 456 pwd := tools.MakeNewPassword("") 457 458 server, err := servers.Create(client, servers.CreateOpts{ 459 Name: name, 460 FlavorRef: choices.FlavorID, 461 ImageRef: choices.ImageID, 462 AdminPass: pwd, 463 Networks: []servers.Network{ 464 {UUID: networkID}, 465 }, 466 Metadata: map[string]string{ 467 "abc": "def", 468 }, 469 Personality: servers.Personality{ 470 &servers.File{ 471 Path: "/etc/test", 472 Contents: []byte("hello world"), 473 }, 474 }, 475 }).Extract() 476 if err != nil { 477 return server, err 478 } 479 480 if err := WaitForComputeStatus(client, server, "ACTIVE"); err != nil { 481 return nil, err 482 } 483 484 newServer, err := servers.Get(client, server.ID).Extract() 485 if err != nil { 486 return nil, err 487 } 488 489 th.AssertEquals(t, newServer.Name, name) 490 th.AssertEquals(t, newServer.Flavor["id"], choices.FlavorID) 491 th.AssertEquals(t, newServer.Image["id"], choices.ImageID) 492 493 return newServer, nil 494 } 495 496 // CreateMicroversionServer creates a basic instance compatible with 497 // newer microversions with a randomly generated name. 498 // The flavor of the instance will be the value of the OS_FLAVOR_ID environment variable. 499 // The image will be the value of the OS_IMAGE_ID environment variable. 500 // The instance will be launched on the network specified in OS_NETWORK_NAME. 501 // An error will be returned if the instance was unable to be created. 502 func CreateMicroversionServer(t *testing.T, client *gophercloud.ServiceClient) (*servers.Server, error) { 503 choices, err := clients.AcceptanceTestChoicesFromEnv() 504 if err != nil { 505 t.Fatal(err) 506 } 507 508 networkID, err := GetNetworkIDFromNetworks(t, client, choices.NetworkName) 509 if err != nil { 510 return nil, err 511 } 512 513 name := tools.RandomString("ACPTTEST", 16) 514 t.Logf("Attempting to create server: %s", name) 515 516 pwd := tools.MakeNewPassword("") 517 518 server, err := servers.Create(client, servers.CreateOpts{ 519 Name: name, 520 FlavorRef: choices.FlavorID, 521 ImageRef: choices.ImageID, 522 AdminPass: pwd, 523 Networks: []servers.Network{ 524 {UUID: networkID}, 525 }, 526 Metadata: map[string]string{ 527 "abc": "def", 528 }, 529 }).Extract() 530 if err != nil { 531 return server, err 532 } 533 534 if err := WaitForComputeStatus(client, server, "ACTIVE"); err != nil { 535 return nil, err 536 } 537 538 newServer, err := servers.Get(client, server.ID).Extract() 539 if err != nil { 540 return nil, err 541 } 542 543 th.AssertEquals(t, newServer.Name, name) 544 th.AssertEquals(t, newServer.Image["id"], choices.ImageID) 545 546 return newServer, nil 547 } 548 549 // CreateServerWithoutImageRef creates a basic instance with a randomly generated name. 550 // The flavor of the instance will be the value of the OS_FLAVOR_ID environment variable. 551 // The image is intentionally missing to trigger an error. 552 // The instance will be launched on the network specified in OS_NETWORK_NAME. 553 // An error will be returned if the instance was unable to be created. 554 func CreateServerWithoutImageRef(t *testing.T, client *gophercloud.ServiceClient) (*servers.Server, error) { 555 choices, err := clients.AcceptanceTestChoicesFromEnv() 556 if err != nil { 557 t.Fatal(err) 558 } 559 560 networkID, err := GetNetworkIDFromNetworks(t, client, choices.NetworkName) 561 if err != nil { 562 return nil, err 563 } 564 565 name := tools.RandomString("ACPTTEST", 16) 566 t.Logf("Attempting to create server: %s", name) 567 568 pwd := tools.MakeNewPassword("") 569 570 server, err := servers.Create(client, servers.CreateOpts{ 571 Name: name, 572 FlavorRef: choices.FlavorID, 573 AdminPass: pwd, 574 Networks: []servers.Network{ 575 {UUID: networkID}, 576 }, 577 Personality: servers.Personality{ 578 &servers.File{ 579 Path: "/etc/test", 580 Contents: []byte("hello world"), 581 }, 582 }, 583 }).Extract() 584 if err != nil { 585 return nil, err 586 } 587 588 if err := WaitForComputeStatus(client, server, "ACTIVE"); err != nil { 589 return nil, err 590 } 591 592 return server, nil 593 } 594 595 // CreateServerWithTags creates a basic instance with a randomly generated name. 596 // The flavor of the instance will be the value of the OS_FLAVOR_ID environment variable. 597 // The image will be the value of the OS_IMAGE_ID environment variable. 598 // The instance will be launched on the network specified in OS_NETWORK_NAME. 599 // Two tags will be assigned to the server. 600 // An error will be returned if the instance was unable to be created. 601 func CreateServerWithTags(t *testing.T, client *gophercloud.ServiceClient, networkID string) (*servers.Server, error) { 602 choices, err := clients.AcceptanceTestChoicesFromEnv() 603 if err != nil { 604 t.Fatal(err) 605 } 606 607 name := tools.RandomString("ACPTTEST", 16) 608 t.Logf("Attempting to create server: %s", name) 609 610 pwd := tools.MakeNewPassword("") 611 612 server, err := servers.Create(client, servers.CreateOpts{ 613 Name: name, 614 FlavorRef: choices.FlavorID, 615 ImageRef: choices.ImageID, 616 AdminPass: pwd, 617 Networks: []servers.Network{ 618 {UUID: networkID}, 619 }, 620 Metadata: map[string]string{ 621 "abc": "def", 622 }, 623 Personality: servers.Personality{ 624 &servers.File{ 625 Path: "/etc/test", 626 Contents: []byte("hello world"), 627 }, 628 }, 629 Tags: []string{"tag1", "tag2"}, 630 }).Extract() 631 if err != nil { 632 return server, err 633 } 634 635 if err := WaitForComputeStatus(client, server, "ACTIVE"); err != nil { 636 return nil, err 637 } 638 639 res := servers.Get(client, server.ID) 640 if res.Err != nil { 641 return nil, res.Err 642 } 643 644 newServer, err := res.Extract() 645 th.AssertNoErr(t, err) 646 th.AssertEquals(t, newServer.Name, name) 647 th.AssertDeepEquals(t, *newServer.Tags, []string{"tag1", "tag2"}) 648 649 return newServer, nil 650 } 651 652 // CreateServerGroup will create a server with a random name. An error will be 653 // returned if the server group failed to be created. 654 func CreateServerGroup(t *testing.T, client *gophercloud.ServiceClient, policy string) (*servergroups.ServerGroup, error) { 655 name := tools.RandomString("ACPTTEST", 16) 656 657 t.Logf("Attempting to create server group %s", name) 658 659 sg, err := servergroups.Create(client, &servergroups.CreateOpts{ 660 Name: name, 661 Policies: []string{policy}, 662 }).Extract() 663 664 if err != nil { 665 return nil, err 666 } 667 668 t.Logf("Successfully created server group %s", name) 669 670 th.AssertEquals(t, sg.Name, name) 671 672 return sg, nil 673 } 674 675 // CreateServerGroupMicroversion will create a server with a random name using 2.64 microversion. An error will be 676 // returned if the server group failed to be created. 677 func CreateServerGroupMicroversion(t *testing.T, client *gophercloud.ServiceClient) (*servergroups.ServerGroup, error) { 678 name := tools.RandomString("ACPTTEST", 16) 679 policy := "anti-affinity" 680 maxServerPerHost := 3 681 682 t.Logf("Attempting to create %s server group with max server per host = %d: %s", policy, maxServerPerHost, name) 683 684 sg, err := servergroups.Create(client, &servergroups.CreateOpts{ 685 Name: name, 686 Policy: policy, 687 Rules: &servergroups.Rules{ 688 MaxServerPerHost: maxServerPerHost, 689 }, 690 }).Extract() 691 692 if err != nil { 693 return nil, err 694 } 695 696 t.Logf("Successfully created server group %s", name) 697 698 th.AssertEquals(t, sg.Name, name) 699 700 return sg, nil 701 } 702 703 // CreateServerInServerGroup works like CreateServer but places the instance in 704 // a specified Server Group. 705 func CreateServerInServerGroup(t *testing.T, client *gophercloud.ServiceClient, serverGroup *servergroups.ServerGroup) (*servers.Server, error) { 706 choices, err := clients.AcceptanceTestChoicesFromEnv() 707 if err != nil { 708 t.Fatal(err) 709 } 710 711 networkID, err := GetNetworkIDFromNetworks(t, client, choices.NetworkName) 712 if err != nil { 713 return nil, err 714 } 715 716 name := tools.RandomString("ACPTTEST", 16) 717 t.Logf("Attempting to create server: %s", name) 718 719 pwd := tools.MakeNewPassword("") 720 721 serverCreateOpts := servers.CreateOpts{ 722 Name: name, 723 FlavorRef: choices.FlavorID, 724 ImageRef: choices.ImageID, 725 AdminPass: pwd, 726 Networks: []servers.Network{ 727 {UUID: networkID}, 728 }, 729 } 730 731 schedulerHintsOpts := schedulerhints.CreateOptsExt{ 732 CreateOptsBuilder: serverCreateOpts, 733 SchedulerHints: schedulerhints.SchedulerHints{ 734 Group: serverGroup.ID, 735 }, 736 } 737 server, err := servers.Create(client, schedulerHintsOpts).Extract() 738 if err != nil { 739 return nil, err 740 } 741 742 if err := WaitForComputeStatus(client, server, "ACTIVE"); err != nil { 743 return nil, err 744 } 745 746 newServer, err := servers.Get(client, server.ID).Extract() 747 if err != nil { 748 return nil, err 749 } 750 751 th.AssertEquals(t, newServer.Name, name) 752 th.AssertEquals(t, newServer.Flavor["id"], choices.FlavorID) 753 th.AssertEquals(t, newServer.Image["id"], choices.ImageID) 754 755 return newServer, nil 756 } 757 758 // CreateServerWithPublicKey works the same as CreateServer, but additionally 759 // configures the server with a specified Key Pair name. 760 func CreateServerWithPublicKey(t *testing.T, client *gophercloud.ServiceClient, keyPairName string) (*servers.Server, error) { 761 choices, err := clients.AcceptanceTestChoicesFromEnv() 762 if err != nil { 763 t.Fatal(err) 764 } 765 766 networkID, err := GetNetworkIDFromNetworks(t, client, choices.NetworkName) 767 if err != nil { 768 return nil, err 769 } 770 771 name := tools.RandomString("ACPTTEST", 16) 772 t.Logf("Attempting to create server: %s", name) 773 774 serverCreateOpts := servers.CreateOpts{ 775 Name: name, 776 FlavorRef: choices.FlavorID, 777 ImageRef: choices.ImageID, 778 Networks: []servers.Network{ 779 {UUID: networkID}, 780 }, 781 } 782 783 server, err := servers.Create(client, keypairs.CreateOptsExt{ 784 CreateOptsBuilder: serverCreateOpts, 785 KeyName: keyPairName, 786 }).Extract() 787 if err != nil { 788 return nil, err 789 } 790 791 if err := WaitForComputeStatus(client, server, "ACTIVE"); err != nil { 792 return nil, err 793 } 794 795 newServer, err := servers.Get(client, server.ID).Extract() 796 if err != nil { 797 return nil, err 798 } 799 800 th.AssertEquals(t, newServer.Name, name) 801 th.AssertEquals(t, newServer.Flavor["id"], choices.FlavorID) 802 th.AssertEquals(t, newServer.Image["id"], choices.ImageID) 803 804 return newServer, nil 805 } 806 807 // CreateVolumeAttachment will attach a volume to a server. An error will be 808 // returned if the volume failed to attach. 809 func CreateVolumeAttachment(t *testing.T, client *gophercloud.ServiceClient, blockClient *gophercloud.ServiceClient, server *servers.Server, volume *volumes.Volume) (*volumeattach.VolumeAttachment, error) { 810 tag := tools.RandomString("ACPTTEST", 16) 811 dot := false 812 813 volumeAttachOptions := volumeattach.CreateOpts{ 814 VolumeID: volume.ID, 815 Tag: tag, 816 DeleteOnTermination: dot, 817 } 818 819 t.Logf("Attempting to attach volume %s to server %s", volume.ID, server.ID) 820 volumeAttachment, err := volumeattach.Create(client, server.ID, volumeAttachOptions).Extract() 821 if err != nil { 822 return volumeAttachment, err 823 } 824 825 if err := volumes.WaitForStatus(blockClient, volume.ID, "in-use", 60); err != nil { 826 return volumeAttachment, err 827 } 828 829 return volumeAttachment, nil 830 } 831 832 // DeleteAggregate will delete a given host aggregate. A fatal error will occur if 833 // the aggregate deleting is failed. This works best when using it as a 834 // deferred function. 835 func DeleteAggregate(t *testing.T, client *gophercloud.ServiceClient, aggregate *aggregates.Aggregate) { 836 err := aggregates.Delete(client, aggregate.ID).ExtractErr() 837 if err != nil { 838 t.Fatalf("Unable to delete aggregate %d", aggregate.ID) 839 } 840 841 t.Logf("Deleted aggregate: %d", aggregate.ID) 842 } 843 844 // DeleteDefaultRule deletes a default security group rule. 845 // A fatal error will occur if the rule failed to delete. This works best when 846 // using it as a deferred function. 847 func DeleteDefaultRule(t *testing.T, client *gophercloud.ServiceClient, defaultRule dsr.DefaultRule) { 848 err := dsr.Delete(client, defaultRule.ID).ExtractErr() 849 if err != nil { 850 t.Fatalf("Unable to delete default rule %s: %v", defaultRule.ID, err) 851 } 852 853 t.Logf("Deleted default rule: %s", defaultRule.ID) 854 } 855 856 // DeleteFlavor will delete a flavor. A fatal error will occur if the flavor 857 // could not be deleted. This works best when using it as a deferred function. 858 func DeleteFlavor(t *testing.T, client *gophercloud.ServiceClient, flavor *flavors.Flavor) { 859 err := flavors.Delete(client, flavor.ID).ExtractErr() 860 if err != nil { 861 t.Fatalf("Unable to delete flavor %s", flavor.ID) 862 } 863 864 t.Logf("Deleted flavor: %s", flavor.ID) 865 } 866 867 // DeleteFloatingIP will de-allocate a floating IP. A fatal error will occur if 868 // the floating IP failed to de-allocate. This works best when using it as a 869 // deferred function. 870 func DeleteFloatingIP(t *testing.T, client *gophercloud.ServiceClient, floatingIP *floatingips.FloatingIP) { 871 err := floatingips.Delete(client, floatingIP.ID).ExtractErr() 872 if err != nil { 873 t.Fatalf("Unable to delete floating IP %s: %v", floatingIP.ID, err) 874 } 875 876 t.Logf("Deleted floating IP: %s", floatingIP.ID) 877 } 878 879 // DeleteKeyPair will delete a specified keypair. A fatal error will occur if 880 // the keypair failed to be deleted. This works best when used as a deferred 881 // function. 882 func DeleteKeyPair(t *testing.T, client *gophercloud.ServiceClient, keyPair *keypairs.KeyPair) { 883 err := keypairs.Delete(client, keyPair.Name, nil).ExtractErr() 884 if err != nil { 885 t.Fatalf("Unable to delete keypair %s: %v", keyPair.Name, err) 886 } 887 888 t.Logf("Deleted keypair: %s", keyPair.Name) 889 } 890 891 // DeleteSecurityGroup will delete a security group. A fatal error will occur 892 // if the group failed to be deleted. This works best as a deferred function. 893 func DeleteSecurityGroup(t *testing.T, client *gophercloud.ServiceClient, securityGroupID string) { 894 err := secgroups.Delete(client, securityGroupID).ExtractErr() 895 if err != nil { 896 t.Fatalf("Unable to delete security group %s: %s", securityGroupID, err) 897 } 898 899 t.Logf("Deleted security group: %s", securityGroupID) 900 } 901 902 // DeleteSecurityGroupRule will delete a security group rule. A fatal error 903 // will occur if the rule failed to be deleted. This works best when used 904 // as a deferred function. 905 func DeleteSecurityGroupRule(t *testing.T, client *gophercloud.ServiceClient, ruleID string) { 906 err := secgroups.DeleteRule(client, ruleID).ExtractErr() 907 if err != nil { 908 t.Fatalf("Unable to delete rule: %v", err) 909 } 910 911 t.Logf("Deleted security group rule: %s", ruleID) 912 } 913 914 // DeleteServer deletes an instance via its UUID. 915 // A fatal error will occur if the instance failed to be destroyed. This works 916 // best when using it as a deferred function. 917 func DeleteServer(t *testing.T, client *gophercloud.ServiceClient, server *servers.Server) { 918 err := servers.Delete(client, server.ID).ExtractErr() 919 if err != nil { 920 t.Fatalf("Unable to delete server %s: %s", server.ID, err) 921 } 922 923 if err := WaitForComputeStatus(client, server, "DELETED"); err != nil { 924 if _, ok := err.(gophercloud.ErrDefault404); ok { 925 t.Logf("Deleted server: %s", server.ID) 926 return 927 } 928 t.Fatalf("Error deleting server %s: %s", server.ID, err) 929 } 930 931 // If we reach this point, the API returned an actual DELETED status 932 // which is a very short window of time, but happens occasionally. 933 t.Logf("Deleted server: %s", server.ID) 934 } 935 936 // DeleteServerGroup will delete a server group. A fatal error will occur if 937 // the server group failed to be deleted. This works best when used as a 938 // deferred function. 939 func DeleteServerGroup(t *testing.T, client *gophercloud.ServiceClient, serverGroup *servergroups.ServerGroup) { 940 err := servergroups.Delete(client, serverGroup.ID).ExtractErr() 941 if err != nil { 942 t.Fatalf("Unable to delete server group %s: %v", serverGroup.ID, err) 943 } 944 945 t.Logf("Deleted server group %s", serverGroup.ID) 946 } 947 948 // DeleteVolumeAttachment will disconnect a volume from an instance. A fatal 949 // error will occur if the volume failed to detach. This works best when used 950 // as a deferred function. 951 func DeleteVolumeAttachment(t *testing.T, client *gophercloud.ServiceClient, blockClient *gophercloud.ServiceClient, server *servers.Server, volumeAttachment *volumeattach.VolumeAttachment) { 952 953 err := volumeattach.Delete(client, server.ID, volumeAttachment.VolumeID).ExtractErr() 954 if err != nil { 955 t.Fatalf("Unable to detach volume: %v", err) 956 } 957 958 if err := volumes.WaitForStatus(blockClient, volumeAttachment.ID, "available", 60); err != nil { 959 t.Fatalf("Unable to wait for volume: %v", err) 960 } 961 t.Logf("Deleted volume: %s", volumeAttachment.VolumeID) 962 } 963 964 // DetachInterface will detach an interface from a server. A fatal 965 // error will occur if the interface could not be detached. This works best 966 // when used as a deferred function. 967 func DetachInterface(t *testing.T, client *gophercloud.ServiceClient, serverID, portID string) { 968 t.Logf("Attempting to detach interface %s from server %s", portID, serverID) 969 970 err := attachinterfaces.Delete(client, serverID, portID).ExtractErr() 971 if err != nil { 972 t.Fatalf("Unable to detach interface %s from server %s", portID, serverID) 973 } 974 975 t.Logf("Detached interface %s from server %s", portID, serverID) 976 } 977 978 // DisassociateFloatingIP will disassociate a floating IP from an instance. A 979 // fatal error will occur if the floating IP failed to disassociate. This works 980 // best when using it as a deferred function. 981 func DisassociateFloatingIP(t *testing.T, client *gophercloud.ServiceClient, floatingIP *floatingips.FloatingIP, server *servers.Server) { 982 disassociateOpts := floatingips.DisassociateOpts{ 983 FloatingIP: floatingIP.IP, 984 } 985 986 err := floatingips.DisassociateInstance(client, server.ID, disassociateOpts).ExtractErr() 987 if err != nil { 988 t.Fatalf("Unable to disassociate floating IP %s from server %s: %v", floatingIP.IP, server.ID, err) 989 } 990 991 t.Logf("Disassociated floating IP %s from server %s", floatingIP.IP, server.ID) 992 } 993 994 // GetNetworkIDFromOSNetworks will return the network ID from a specified network 995 // UUID using the os-networks API extension. An error will be returned if the 996 // network could not be retrieved. 997 func GetNetworkIDFromOSNetworks(t *testing.T, client *gophercloud.ServiceClient, networkName string) (string, error) { 998 allPages, err := networks.List(client).AllPages() 999 if err != nil { 1000 t.Fatalf("Unable to list networks: %v", err) 1001 } 1002 1003 networkList, err := networks.ExtractNetworks(allPages) 1004 if err != nil { 1005 t.Fatalf("Unable to list networks: %v", err) 1006 } 1007 1008 networkID := "" 1009 for _, network := range networkList { 1010 t.Logf("Network: %v", network) 1011 if network.Label == networkName { 1012 networkID = network.ID 1013 } 1014 } 1015 1016 t.Logf("Found network ID for %s: %s", networkName, networkID) 1017 1018 return networkID, nil 1019 } 1020 1021 // GetNetworkIDFromTenantNetworks will return the network UUID for a given 1022 // network name using the os-tenant-networks API extension. An error will be 1023 // returned if the network could not be retrieved. 1024 func GetNetworkIDFromTenantNetworks(t *testing.T, client *gophercloud.ServiceClient, networkName string) (string, error) { 1025 allPages, err := tenantnetworks.List(client).AllPages() 1026 if err != nil { 1027 return "", err 1028 } 1029 1030 allTenantNetworks, err := tenantnetworks.ExtractNetworks(allPages) 1031 if err != nil { 1032 return "", err 1033 } 1034 1035 for _, network := range allTenantNetworks { 1036 if network.Name == networkName { 1037 return network.ID, nil 1038 } 1039 } 1040 1041 return "", fmt.Errorf("Failed to obtain network ID for network %s", networkName) 1042 } 1043 1044 // GetNetworkIDFromNetworks will return the network UUID for a given network 1045 // name using either the os-tenant-networks API extension or Neutron API. 1046 // An error will be returned if the network could not be retrieved. 1047 func GetNetworkIDFromNetworks(t *testing.T, client *gophercloud.ServiceClient, networkName string) (string, error) { 1048 allPages, err := tenantnetworks.List(client).AllPages() 1049 if err == nil { 1050 allTenantNetworks, err := tenantnetworks.ExtractNetworks(allPages) 1051 if err != nil { 1052 return "", err 1053 } 1054 1055 for _, network := range allTenantNetworks { 1056 if network.Name == networkName { 1057 return network.ID, nil 1058 } 1059 } 1060 } 1061 1062 networkClient, err := clients.NewNetworkV2Client() 1063 th.AssertNoErr(t, err) 1064 1065 allPages2, err := neutron.List(networkClient, nil).AllPages() 1066 th.AssertNoErr(t, err) 1067 1068 allNetworks, err := neutron.ExtractNetworks(allPages2) 1069 th.AssertNoErr(t, err) 1070 1071 for _, network := range allNetworks { 1072 if network.Name == networkName { 1073 return network.ID, nil 1074 } 1075 } 1076 1077 return "", fmt.Errorf("Failed to obtain network ID for network %s", networkName) 1078 } 1079 1080 // ImportPublicKey will create a KeyPair with a random name and a specified 1081 // public key. An error will be returned if the keypair failed to be created. 1082 func ImportPublicKey(t *testing.T, client *gophercloud.ServiceClient, publicKey string) (*keypairs.KeyPair, error) { 1083 keyPairName := tools.RandomString("keypair_", 5) 1084 1085 t.Logf("Attempting to create keypair: %s", keyPairName) 1086 createOpts := keypairs.CreateOpts{ 1087 Name: keyPairName, 1088 PublicKey: publicKey, 1089 } 1090 keyPair, err := keypairs.Create(client, createOpts).Extract() 1091 if err != nil { 1092 return keyPair, err 1093 } 1094 1095 t.Logf("Created keypair: %s", keyPairName) 1096 1097 th.AssertEquals(t, keyPair.Name, keyPairName) 1098 th.AssertEquals(t, keyPair.PublicKey, publicKey) 1099 1100 return keyPair, nil 1101 } 1102 1103 // ResizeServer performs a resize action on an instance. An error will be 1104 // returned if the instance failed to resize. 1105 // The new flavor that the instance will be resized to is specified in OS_FLAVOR_ID_RESIZE. 1106 func ResizeServer(t *testing.T, client *gophercloud.ServiceClient, server *servers.Server) error { 1107 choices, err := clients.AcceptanceTestChoicesFromEnv() 1108 if err != nil { 1109 t.Fatal(err) 1110 } 1111 1112 opts := &servers.ResizeOpts{ 1113 FlavorRef: choices.FlavorIDResize, 1114 } 1115 if res := servers.Resize(client, server.ID, opts); res.Err != nil { 1116 return res.Err 1117 } 1118 1119 if err := WaitForComputeStatus(client, server, "VERIFY_RESIZE"); err != nil { 1120 return err 1121 } 1122 1123 return nil 1124 } 1125 1126 // WaitForComputeStatus will poll an instance's status until it either matches 1127 // the specified status or the status becomes ERROR. 1128 func WaitForComputeStatus(client *gophercloud.ServiceClient, server *servers.Server, status string) error { 1129 return tools.WaitFor(func() (bool, error) { 1130 latest, err := servers.Get(client, server.ID).Extract() 1131 if err != nil { 1132 return false, err 1133 } 1134 1135 if latest.Status == status { 1136 // Success! 1137 return true, nil 1138 } 1139 1140 if latest.Status == "ERROR" { 1141 return false, fmt.Errorf("Instance in ERROR state") 1142 } 1143 1144 return false, nil 1145 }) 1146 } 1147 1148 // Convenience method to fill an QuotaSet-UpdateOpts-struct from a QuotaSet-struct 1149 func FillUpdateOptsFromQuotaSet(src quotasets.QuotaSet, dest *quotasets.UpdateOpts) { 1150 dest.FixedIPs = &src.FixedIPs 1151 dest.FloatingIPs = &src.FloatingIPs 1152 dest.InjectedFileContentBytes = &src.InjectedFileContentBytes 1153 dest.InjectedFilePathBytes = &src.InjectedFilePathBytes 1154 dest.InjectedFiles = &src.InjectedFiles 1155 dest.KeyPairs = &src.KeyPairs 1156 dest.RAM = &src.RAM 1157 dest.SecurityGroupRules = &src.SecurityGroupRules 1158 dest.SecurityGroups = &src.SecurityGroups 1159 dest.Cores = &src.Cores 1160 dest.Instances = &src.Instances 1161 dest.ServerGroups = &src.ServerGroups 1162 dest.ServerGroupMembers = &src.ServerGroupMembers 1163 dest.MetadataItems = &src.MetadataItems 1164 } 1165 1166 // RescueServer will place the specified server into rescue mode. 1167 func RescueServer(t *testing.T, client *gophercloud.ServiceClient, server *servers.Server) error { 1168 t.Logf("Attempting to put server %s into rescue mode", server.ID) 1169 _, err := rescueunrescue.Rescue(client, server.ID, rescueunrescue.RescueOpts{}).Extract() 1170 if err != nil { 1171 return err 1172 } 1173 1174 if err := WaitForComputeStatus(client, server, "RESCUE"); err != nil { 1175 return err 1176 } 1177 1178 return nil 1179 } 1180 1181 // UnrescueServer will return server from rescue mode. 1182 func UnrescueServer(t *testing.T, client *gophercloud.ServiceClient, server *servers.Server) error { 1183 t.Logf("Attempting to return server %s from rescue mode", server.ID) 1184 if err := rescueunrescue.Unrescue(client, server.ID).ExtractErr(); err != nil { 1185 return err 1186 } 1187 1188 if err := WaitForComputeStatus(client, server, "ACTIVE"); err != nil { 1189 return err 1190 } 1191 1192 return nil 1193 } 1194 1195 // CreateRemoteConsole will create a remote noVNC console for the specified server. 1196 func CreateRemoteConsole(t *testing.T, client *gophercloud.ServiceClient, serverID string) (*remoteconsoles.RemoteConsole, error) { 1197 createOpts := remoteconsoles.CreateOpts{ 1198 Protocol: remoteconsoles.ConsoleProtocolVNC, 1199 Type: remoteconsoles.ConsoleTypeNoVNC, 1200 } 1201 1202 t.Logf("Attempting to create a %s console for the server %s", createOpts.Type, serverID) 1203 remoteConsole, err := remoteconsoles.Create(client, serverID, createOpts).Extract() 1204 if err != nil { 1205 return nil, err 1206 } 1207 1208 t.Logf("Successfully created console: %s", remoteConsole.URL) 1209 return remoteConsole, nil 1210 } 1211 1212 // CreateNoNetworkServer creates a basic instance with a randomly generated name. 1213 // The flavor of the instance will be the value of the OS_FLAVOR_ID environment variable. 1214 // The image will be the value of the OS_IMAGE_ID environment variable. 1215 // The instance will be launched without network interfaces attached. 1216 // An error will be returned if the instance was unable to be created. 1217 func CreateServerNoNetwork(t *testing.T, client *gophercloud.ServiceClient) (*servers.Server, error) { 1218 choices, err := clients.AcceptanceTestChoicesFromEnv() 1219 if err != nil { 1220 t.Fatal(err) 1221 } 1222 1223 name := tools.RandomString("ACPTTEST", 16) 1224 t.Logf("Attempting to create server: %s", name) 1225 1226 pwd := tools.MakeNewPassword("") 1227 1228 server, err := servers.Create(client, servers.CreateOpts{ 1229 Name: name, 1230 FlavorRef: choices.FlavorID, 1231 ImageRef: choices.ImageID, 1232 AdminPass: pwd, 1233 Networks: "none", 1234 Metadata: map[string]string{ 1235 "abc": "def", 1236 }, 1237 Personality: servers.Personality{ 1238 &servers.File{ 1239 Path: "/etc/test", 1240 Contents: []byte("hello world"), 1241 }, 1242 }, 1243 }).Extract() 1244 if err != nil { 1245 return server, err 1246 } 1247 1248 if err := WaitForComputeStatus(client, server, "ACTIVE"); err != nil { 1249 return nil, err 1250 } 1251 1252 newServer, err := servers.Get(client, server.ID).Extract() 1253 if err != nil { 1254 return nil, err 1255 } 1256 1257 th.AssertEquals(t, newServer.Name, name) 1258 th.AssertEquals(t, newServer.Flavor["id"], choices.FlavorID) 1259 th.AssertEquals(t, newServer.Image["id"], choices.ImageID) 1260 1261 return newServer, nil 1262 }