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