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