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  }