Compute Acceptance Test Changes (#33)
This commit makes more changes to the compute acceptance tests:
* Makes all reusable functions exportable so other APIs can use them.
* Centralizes client initialization and environment variable checks.
diff --git a/acceptance/openstack/compute/v2/compute.go b/acceptance/openstack/compute/v2/compute.go
new file mode 100644
index 0000000..80bdf86
--- /dev/null
+++ b/acceptance/openstack/compute/v2/compute.go
@@ -0,0 +1,773 @@
+// Package v2 contains common functions for creating compute-based resources
+// for use in acceptance tests. See the `*_test.go` files for example usages.
+package v2
+
+import (
+ "crypto/rand"
+ "crypto/rsa"
+ "fmt"
+ "testing"
+
+ "github.com/gophercloud/gophercloud"
+ "github.com/gophercloud/gophercloud/acceptance/clients"
+ "github.com/gophercloud/gophercloud/acceptance/tools"
+ "github.com/gophercloud/gophercloud/openstack/blockstorage/v1/volumes"
+ "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/bootfromvolume"
+ dsr "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/defsecrules"
+ "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/floatingips"
+ "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/keypairs"
+ "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/networks"
+ "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/quotasets"
+ "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/schedulerhints"
+ "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/secgroups"
+ "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/servergroups"
+ "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/tenantnetworks"
+ "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach"
+ "github.com/gophercloud/gophercloud/openstack/compute/v2/flavors"
+ "github.com/gophercloud/gophercloud/openstack/compute/v2/images"
+ "github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
+
+ "golang.org/x/crypto/ssh"
+)
+
+// AssociateFloatingIP will associate a floating IP with an instance. An error
+// will be returned if the floating IP was unable to be associated.
+func AssociateFloatingIP(t *testing.T, client *gophercloud.ServiceClient, floatingIP *floatingips.FloatingIP, server *servers.Server) error {
+ associateOpts := floatingips.AssociateOpts{
+ FloatingIP: floatingIP.IP,
+ }
+
+ t.Logf("Attempting to associate floating IP %s to instance %s", floatingIP.IP, server.ID)
+ err := floatingips.AssociateInstance(client, server.ID, associateOpts).ExtractErr()
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// AssociateFloatingIPWithFixedIP will associate a floating IP with an
+// instance's specific fixed IP. An error will be returend if the floating IP
+// was unable to be associated.
+func AssociateFloatingIPWithFixedIP(t *testing.T, client *gophercloud.ServiceClient, floatingIP *floatingips.FloatingIP, server *servers.Server, fixedIP string) error {
+ associateOpts := floatingips.AssociateOpts{
+ FloatingIP: floatingIP.IP,
+ FixedIP: fixedIP,
+ }
+
+ t.Logf("Attempting to associate floating IP %s to fixed IP %s on instance %s", floatingIP.IP, fixedIP, server.ID)
+ err := floatingips.AssociateInstance(client, server.ID, associateOpts).ExtractErr()
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// CreateBootableVolumeServer works like CreateServer but is configured with
+// one or more block devices defined by passing in []bootfromvolume.BlockDevice.
+// An error will be returned if a server was unable to be created.
+func CreateBootableVolumeServer(t *testing.T, client *gophercloud.ServiceClient, blockDevices []bootfromvolume.BlockDevice, choices *clients.AcceptanceTestChoices) (*servers.Server, error) {
+ if testing.Short() {
+ t.Skip("Skipping test that requires server creation in short mode.")
+ }
+
+ var server *servers.Server
+
+ networkID, err := GetNetworkIDFromTenantNetworks(t, client, choices.NetworkName)
+ if err != nil {
+ return server, err
+ }
+
+ name := tools.RandomString("ACPTTEST", 16)
+ t.Logf("Attempting to create bootable volume server: %s", name)
+
+ serverCreateOpts := servers.CreateOpts{
+ Name: name,
+ FlavorRef: choices.FlavorID,
+ ImageRef: choices.ImageID,
+ Networks: []servers.Network{
+ servers.Network{UUID: networkID},
+ },
+ }
+
+ server, err = bootfromvolume.Create(client, bootfromvolume.CreateOptsExt{
+ serverCreateOpts,
+ blockDevices,
+ }).Extract()
+
+ if err != nil {
+ return server, err
+ }
+
+ return server, nil
+}
+
+// CreateDefaultRule will create a default security group rule with a
+// random port range between 80 and 90. An error will be returned if
+// a default rule was unable to be created.
+func CreateDefaultRule(t *testing.T, client *gophercloud.ServiceClient) (dsr.DefaultRule, error) {
+ createOpts := dsr.CreateOpts{
+ FromPort: tools.RandomInt(80, 89),
+ ToPort: tools.RandomInt(90, 99),
+ IPProtocol: "TCP",
+ CIDR: "0.0.0.0/0",
+ }
+
+ defaultRule, err := dsr.Create(client, createOpts).Extract()
+ if err != nil {
+ return *defaultRule, err
+ }
+
+ t.Logf("Created default rule: %s", defaultRule.ID)
+
+ return *defaultRule, nil
+}
+
+// CreateFloatingIP will allocate a floating IP.
+// An error will be returend if one was unable to be allocated.
+func CreateFloatingIP(t *testing.T, client *gophercloud.ServiceClient, choices *clients.AcceptanceTestChoices) (*floatingips.FloatingIP, error) {
+ createOpts := floatingips.CreateOpts{
+ Pool: choices.FloatingIPPoolName,
+ }
+ floatingIP, err := floatingips.Create(client, createOpts).Extract()
+ if err != nil {
+ return floatingIP, err
+ }
+
+ t.Logf("Created floating IP: %s", floatingIP.ID)
+ return floatingIP, nil
+}
+
+func createKey() (string, error) {
+ privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
+ if err != nil {
+ return "", err
+ }
+
+ publicKey := privateKey.PublicKey
+ pub, err := ssh.NewPublicKey(&publicKey)
+ if err != nil {
+ return "", err
+ }
+
+ pubBytes := ssh.MarshalAuthorizedKey(pub)
+ pk := string(pubBytes)
+ return pk, nil
+}
+
+// CreateKeyPair will create a KeyPair with a random name. An error will occur
+// if the keypair failed to be created. An error will be returned if the
+// keypair was unable to be created.
+func CreateKeyPair(t *testing.T, client *gophercloud.ServiceClient) (*keypairs.KeyPair, error) {
+ keyPairName := tools.RandomString("keypair_", 5)
+
+ t.Logf("Attempting to create keypair: %s", keyPairName)
+ createOpts := keypairs.CreateOpts{
+ Name: keyPairName,
+ }
+ keyPair, err := keypairs.Create(client, createOpts).Extract()
+ if err != nil {
+ return keyPair, err
+ }
+
+ t.Logf("Created keypair: %s", keyPairName)
+ return keyPair, nil
+}
+
+// CreateSecurityGroup will create a security group with a random name.
+// An error will be returned if one was failed to be created.
+func CreateSecurityGroup(t *testing.T, client *gophercloud.ServiceClient) (secgroups.SecurityGroup, error) {
+ createOpts := secgroups.CreateOpts{
+ Name: tools.RandomString("secgroup_", 5),
+ Description: "something",
+ }
+
+ securityGroup, err := secgroups.Create(client, createOpts).Extract()
+ if err != nil {
+ return *securityGroup, err
+ }
+
+ t.Logf("Created security group: %s", securityGroup.ID)
+ return *securityGroup, nil
+}
+
+// CreateSecurityGroupRule will create a security group rule with a random name
+// and a random TCP port range between port 80 and 99. An error will be
+// returned if the rule failed to be created.
+func CreateSecurityGroupRule(t *testing.T, client *gophercloud.ServiceClient, securityGroupID string) (secgroups.Rule, error) {
+ createOpts := secgroups.CreateRuleOpts{
+ ParentGroupID: securityGroupID,
+ FromPort: tools.RandomInt(80, 89),
+ ToPort: tools.RandomInt(90, 99),
+ IPProtocol: "TCP",
+ CIDR: "0.0.0.0/0",
+ }
+
+ rule, err := secgroups.CreateRule(client, createOpts).Extract()
+ if err != nil {
+ return *rule, err
+ }
+
+ t.Logf("Created security group rule: %s", rule.ID)
+ return *rule, nil
+}
+
+// CreateServer creates a basic instance with a randomly generated name.
+// The flavor of the instance will be the value of the OS_FLAVOR_ID environment variable.
+// The image will be the value of the OS_IMAGE_ID environment variable.
+// The instance will be launched on the network specified in OS_NETWORK_NAME.
+// An error will be returned if the instance was unable to be created.
+func CreateServer(t *testing.T, client *gophercloud.ServiceClient, choices *clients.AcceptanceTestChoices) (*servers.Server, error) {
+ if testing.Short() {
+ t.Skip("Skipping test that requires server creation in short mode.")
+ }
+
+ var server *servers.Server
+
+ networkID, err := GetNetworkIDFromTenantNetworks(t, client, choices.NetworkName)
+ if err != nil {
+ return server, err
+ }
+
+ name := tools.RandomString("ACPTTEST", 16)
+ t.Logf("Attempting to create server: %s", name)
+
+ pwd := tools.MakeNewPassword("")
+
+ server, err = servers.Create(client, servers.CreateOpts{
+ Name: name,
+ FlavorRef: choices.FlavorID,
+ ImageRef: choices.ImageID,
+ AdminPass: pwd,
+ Networks: []servers.Network{
+ servers.Network{UUID: networkID},
+ },
+ Personality: servers.Personality{
+ &servers.File{
+ Path: "/etc/test",
+ Contents: []byte("hello world"),
+ },
+ },
+ }).Extract()
+ if err != nil {
+ return server, err
+ }
+
+ if err = WaitForComputeStatus(client, server, "ACTIVE"); err != nil {
+ return server, err
+ }
+
+ return server, nil
+}
+
+// CreateServerGroup will create a server with a random name. An error will be
+// returned if the server group failed to be created.
+func CreateServerGroup(t *testing.T, client *gophercloud.ServiceClient, policy string) (*servergroups.ServerGroup, error) {
+ sg, err := servergroups.Create(client, &servergroups.CreateOpts{
+ Name: "test",
+ Policies: []string{policy},
+ }).Extract()
+
+ if err != nil {
+ return sg, err
+ }
+
+ return sg, nil
+}
+
+// CreateServerInServerGroup works like CreateServer but places the instance in
+// a specified Server Group.
+func CreateServerInServerGroup(t *testing.T, client *gophercloud.ServiceClient, choices *clients.AcceptanceTestChoices, serverGroup *servergroups.ServerGroup) (*servers.Server, error) {
+ if testing.Short() {
+ t.Skip("Skipping test that requires server creation in short mode.")
+ }
+
+ var server *servers.Server
+
+ networkID, err := GetNetworkIDFromTenantNetworks(t, client, choices.NetworkName)
+ if err != nil {
+ return server, err
+ }
+
+ name := tools.RandomString("ACPTTEST", 16)
+ t.Logf("Attempting to create server: %s", name)
+
+ pwd := tools.MakeNewPassword("")
+
+ serverCreateOpts := servers.CreateOpts{
+ Name: name,
+ FlavorRef: choices.FlavorID,
+ ImageRef: choices.ImageID,
+ AdminPass: pwd,
+ Networks: []servers.Network{
+ servers.Network{UUID: networkID},
+ },
+ }
+
+ schedulerHintsOpts := schedulerhints.CreateOptsExt{
+ serverCreateOpts,
+ schedulerhints.SchedulerHints{
+ Group: serverGroup.ID,
+ },
+ }
+ server, err = servers.Create(client, schedulerHintsOpts).Extract()
+ if err != nil {
+ return server, err
+ }
+
+ return server, nil
+}
+
+// CreateServerWithPublicKey works the same as CreateServer, but additionally
+// configures the server with a specified Key Pair name.
+func CreateServerWithPublicKey(t *testing.T, client *gophercloud.ServiceClient, choices *clients.AcceptanceTestChoices, keyPairName string) (*servers.Server, error) {
+ if testing.Short() {
+ t.Skip("Skipping test that requires server creation in short mode.")
+ }
+
+ var server *servers.Server
+
+ networkID, err := GetNetworkIDFromTenantNetworks(t, client, choices.NetworkName)
+ if err != nil {
+ return server, err
+ }
+
+ name := tools.RandomString("ACPTTEST", 16)
+ t.Logf("Attempting to create server: %s", name)
+
+ serverCreateOpts := servers.CreateOpts{
+ Name: name,
+ FlavorRef: choices.FlavorID,
+ ImageRef: choices.ImageID,
+ Networks: []servers.Network{
+ servers.Network{UUID: networkID},
+ },
+ }
+
+ server, err = servers.Create(client, keypairs.CreateOptsExt{
+ serverCreateOpts,
+ keyPairName,
+ }).Extract()
+ if err != nil {
+ return server, err
+ }
+
+ if err = WaitForComputeStatus(client, server, "ACTIVE"); err != nil {
+ return server, err
+ }
+
+ return server, nil
+}
+
+// CreateVolumeAttachment will attach a volume to a servert. An error will be
+// returned if the volume failed to attach.
+func CreateVolumeAttachment(t *testing.T, client *gophercloud.ServiceClient, blockClient *gophercloud.ServiceClient, server *servers.Server, volume *volumes.Volume) (*volumeattach.VolumeAttachment, error) {
+ volumeAttachOptions := volumeattach.CreateOpts{
+ VolumeID: volume.ID,
+ }
+
+ t.Logf("Attempting to attach volume %s to server %s", volume.ID, server.ID)
+ volumeAttachment, err := volumeattach.Create(client, server.ID, volumeAttachOptions).Extract()
+ if err != nil {
+ return volumeAttachment, err
+ }
+
+ if err = volumes.WaitForStatus(blockClient, volume.ID, "in-use", 60); err != nil {
+ return volumeAttachment, err
+ }
+
+ return volumeAttachment, nil
+}
+
+// DeleteDefaultRule deletes a default security group rule.
+// A fatal error will occur if the rule failed to delete. This works best when
+// using it as a deferred function.
+func DeleteDefaultRule(t *testing.T, client *gophercloud.ServiceClient, defaultRule dsr.DefaultRule) {
+ err := dsr.Delete(client, defaultRule.ID).ExtractErr()
+ if err != nil {
+ t.Fatalf("Unable to delete default rule %s: %v", defaultRule.ID, err)
+ }
+
+ t.Logf("Deleted default rule: %s", defaultRule.ID)
+}
+
+// DeleteFloatingIP will de-allocate a floating IP. A fatal error will occur if
+// the floating IP failed to de-allocate. This works best when using it as a
+// deferred function.
+func DeleteFloatingIP(t *testing.T, client *gophercloud.ServiceClient, floatingIP *floatingips.FloatingIP) {
+ err := floatingips.Delete(client, floatingIP.ID).ExtractErr()
+ if err != nil {
+ t.Fatalf("Unable to delete floating IP %s: %v", floatingIP.ID, err)
+ }
+
+ t.Logf("Deleted floating IP: %s", floatingIP.ID)
+}
+
+// DeleteKeyPair will delete a specified keypair. A fatal error will occur if
+// the keypair failed to be deleted. This works best when used as a deferred
+// function.
+func DeleteKeyPair(t *testing.T, client *gophercloud.ServiceClient, keyPair *keypairs.KeyPair) {
+ err := keypairs.Delete(client, keyPair.Name).ExtractErr()
+ if err != nil {
+ t.Fatalf("Unable to delete keypair %s: %v", keyPair.Name, err)
+ }
+
+ t.Logf("Deleted keypair: %s", keyPair.Name)
+}
+
+// DeleteSecurityGroup will delete a security group. A fatal error will occur
+// if the group failed to be deleted. This works best as a deferred function.
+func DeleteSecurityGroup(t *testing.T, client *gophercloud.ServiceClient, securityGroup secgroups.SecurityGroup) {
+ err := secgroups.Delete(client, securityGroup.ID).ExtractErr()
+ if err != nil {
+ t.Fatalf("Unable to delete security group %s: %s", securityGroup.ID, err)
+ }
+
+ t.Logf("Deleted security group: %s", securityGroup.ID)
+}
+
+// DeleteSecurityGroupRule will delete a security group rule. A fatal error
+// will occur if the rule failed to be deleted. This works best when used
+// as a deferred function.
+func DeleteSecurityGroupRule(t *testing.T, client *gophercloud.ServiceClient, rule secgroups.Rule) {
+ err := secgroups.DeleteRule(client, rule.ID).ExtractErr()
+ if err != nil {
+ t.Fatalf("Unable to delete rule: %v", err)
+ }
+
+ t.Logf("Deleted security group rule: %s", rule.ID)
+}
+
+// DeleteServer deletes an instance via its UUID.
+// A fatal error will occur if the instance failed to be destroyed. This works
+// best when using it as a deferred function.
+func DeleteServer(t *testing.T, client *gophercloud.ServiceClient, server *servers.Server) {
+ err := servers.Delete(client, server.ID).ExtractErr()
+ if err != nil {
+ t.Fatalf("Unable to delete server %s: %s", server.ID, err)
+ }
+
+ t.Logf("Deleted server: %s", server.ID)
+}
+
+// DeleteServerGroup will delete a server group. A fatal error will occur if
+// the server group failed to be deleted. This works best when used as a
+// deferred function.
+func DeleteServerGroup(t *testing.T, client *gophercloud.ServiceClient, serverGroup *servergroups.ServerGroup) {
+ err := servergroups.Delete(client, serverGroup.ID).ExtractErr()
+ if err != nil {
+ t.Fatalf("Unable to delete server group %s: %v", serverGroup.ID, err)
+ }
+
+ t.Logf("Deleted server group %s", serverGroup.ID)
+}
+
+// DeleteVolumeAttachment will disconnect a volume from an instance. A fatal
+// error will occur if the volume failed to detach. This works best when used
+// as a deferred functino.
+func DeleteVolumeAttachment(t *testing.T, client *gophercloud.ServiceClient, blockClient *gophercloud.ServiceClient, server *servers.Server, volumeAttachment *volumeattach.VolumeAttachment) {
+
+ err := volumeattach.Delete(client, server.ID, volumeAttachment.VolumeID).ExtractErr()
+ if err != nil {
+ t.Fatalf("Unable to detach volume: %v", err)
+ }
+
+ if err = volumes.WaitForStatus(blockClient, volumeAttachment.ID, "available", 60); err != nil {
+ t.Fatalf("Unable to wait for volume: %v", err)
+ }
+ t.Logf("Deleted volume: %s", volumeAttachment.VolumeID)
+}
+
+// DisassociateFloatingIP will disassociate a floating IP from an instance. A
+// fatal error will occur if the floating IP failed to disassociate. This works
+// best when using it as a deferred function.
+func DisassociateFloatingIP(t *testing.T, client *gophercloud.ServiceClient, floatingIP *floatingips.FloatingIP, server *servers.Server) {
+ disassociateOpts := floatingips.DisassociateOpts{
+ FloatingIP: floatingIP.IP,
+ }
+
+ err := floatingips.DisassociateInstance(client, server.ID, disassociateOpts).ExtractErr()
+ if err != nil {
+ t.Fatalf("Unable to disassociate floating IP %s from server %s: %v", floatingIP.IP, server.ID, err)
+ }
+
+ t.Logf("Disassociated floating IP %s from server %s", floatingIP.IP, server.ID)
+}
+
+// GetNetworkIDFromNetworks will return the network ID from a specified network
+// UUID using the os-networks API extension. An error will be returned if the
+// network could not be retrieved.
+func GetNetworkIDFromNetworks(t *testing.T, client *gophercloud.ServiceClient, networkName string) (string, error) {
+ allPages, err := networks.List(client).AllPages()
+ if err != nil {
+ t.Fatalf("Unable to list networks: %v", err)
+ }
+
+ networkList, err := networks.ExtractNetworks(allPages)
+ if err != nil {
+ t.Fatalf("Unable to list networks: %v", err)
+ }
+
+ networkID := ""
+ for _, network := range networkList {
+ t.Logf("Network: %v", network)
+ if network.Label == networkName {
+ networkID = network.ID
+ }
+ }
+
+ t.Logf("Found network ID for %s: %s", networkName, networkID)
+
+ return networkID, nil
+}
+
+// GetNetworkIDFromTenantNetworks will return the network UUID for a given
+// network name using the os-tenant-networks API extension. An error will be
+// returned if the network could not be retrieved.
+func GetNetworkIDFromTenantNetworks(t *testing.T, client *gophercloud.ServiceClient, networkName string) (string, error) {
+ allPages, err := tenantnetworks.List(client).AllPages()
+ if err != nil {
+ return "", err
+ }
+
+ allTenantNetworks, err := tenantnetworks.ExtractNetworks(allPages)
+ if err != nil {
+ return "", err
+ }
+
+ for _, network := range allTenantNetworks {
+ if network.Name == networkName {
+ return network.ID, nil
+ }
+ }
+
+ return "", fmt.Errorf("Failed to obtain network ID for network %s", networkName)
+}
+
+// ImportPublicKey will create a KeyPair with a random name and a specified
+// public key. An error will be returned if the keypair failed to be created.
+func ImportPublicKey(t *testing.T, client *gophercloud.ServiceClient, publicKey string) (*keypairs.KeyPair, error) {
+ keyPairName := tools.RandomString("keypair_", 5)
+
+ t.Logf("Attempting to create keypair: %s", keyPairName)
+ createOpts := keypairs.CreateOpts{
+ Name: keyPairName,
+ PublicKey: publicKey,
+ }
+ keyPair, err := keypairs.Create(client, createOpts).Extract()
+ if err != nil {
+ return keyPair, err
+ }
+
+ t.Logf("Created keypair: %s", keyPairName)
+ return keyPair, nil
+}
+
+// ResizeServer performs a resize action on an instance. An error will be
+// returned if the instance failed to resize.
+// The new flavor that the instance will be resized to is specified in OS_FLAVOR_ID_RESIZE.
+func ResizeServer(t *testing.T, client *gophercloud.ServiceClient, server *servers.Server, choices *clients.AcceptanceTestChoices) error {
+ opts := &servers.ResizeOpts{
+ FlavorRef: choices.FlavorIDResize,
+ }
+ if res := servers.Resize(client, server.ID, opts); res.Err != nil {
+ return res.Err
+ }
+
+ if err := WaitForComputeStatus(client, server, "VERIFY_RESIZE"); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// WaitForComputeStatus will poll an instance's status until it either matches
+// the specified status or the status becomes ERROR.
+func WaitForComputeStatus(client *gophercloud.ServiceClient, server *servers.Server, status string) error {
+ return tools.WaitFor(func() (bool, error) {
+ latest, err := servers.Get(client, server.ID).Extract()
+ if err != nil {
+ return false, err
+ }
+
+ if latest.Status == status {
+ // Success!
+ return true, nil
+ }
+
+ if latest.Status == "ERROR" {
+ return false, fmt.Errorf("Instance in ERROR state")
+ }
+
+ return false, nil
+ })
+}
+
+// PrintServer will print an instance and all of its attributes.
+func PrintServer(t *testing.T, server *servers.Server) {
+ t.Logf("ID: %s", server.ID)
+ t.Logf("TenantID: %s", server.TenantID)
+ t.Logf("UserID: %s", server.UserID)
+ t.Logf("Name: %s", server.Name)
+ t.Logf("Updated: %s", server.Updated)
+ t.Logf("Created: %s", server.Created)
+ t.Logf("HostID: %s", server.HostID)
+ t.Logf("Status: %s", server.Status)
+ t.Logf("Progress: %d", server.Progress)
+ t.Logf("AccessIPv4: %s", server.AccessIPv4)
+ t.Logf("AccessIPv6: %s", server.AccessIPv6)
+ t.Logf("Image: %s", server.Image)
+ t.Logf("Flavor: %s", server.Flavor)
+ t.Logf("Addresses: %#v", server.Addresses)
+ t.Logf("Metadata: %#v", server.Metadata)
+ t.Logf("Links: %#v", server.Links)
+ t.Logf("KeyName: %s", server.KeyName)
+ t.Logf("AdminPass: %s", server.AdminPass)
+ t.Logf("SecurityGroups: %#v", server.SecurityGroups)
+}
+
+// PrintDefaultRule will print a default security group rule and all of its attributes.
+func PrintDefaultRule(t *testing.T, defaultRule *dsr.DefaultRule) {
+ t.Logf("\tID: %s", defaultRule.ID)
+ t.Logf("\tFrom Port: %d", defaultRule.FromPort)
+ t.Logf("\tTo Port: %d", defaultRule.ToPort)
+ t.Logf("\tIP Protocol: %s", defaultRule.IPProtocol)
+ t.Logf("\tIP Range: %s", defaultRule.IPRange.CIDR)
+ t.Logf("\tParent Group ID: %s", defaultRule.ParentGroupID)
+ t.Logf("\tGroup Tenant ID: %s", defaultRule.Group.TenantID)
+ t.Logf("\tGroup Name: %s", defaultRule.Group.Name)
+}
+
+// PrintFlavor will print a flavor and all of its attributes.
+func PrintFlavor(t *testing.T, flavor *flavors.Flavor) {
+ t.Logf("ID: %s", flavor.ID)
+ t.Logf("Name: %s", flavor.Name)
+ t.Logf("RAM: %d", flavor.RAM)
+ t.Logf("Disk: %d", flavor.Disk)
+ t.Logf("Swap: %d", flavor.Swap)
+ t.Logf("RxTxFactor: %f", flavor.RxTxFactor)
+}
+
+// PrintFloatingIP will print a floating IP and all of its attributes.
+func PrintFloatingIP(t *testing.T, floatingIP *floatingips.FloatingIP) {
+ t.Logf("ID: %s", floatingIP.ID)
+ t.Logf("Fixed IP: %s", floatingIP.FixedIP)
+ t.Logf("Instance ID: %s", floatingIP.InstanceID)
+ t.Logf("IP: %s", floatingIP.IP)
+ t.Logf("Pool: %s", floatingIP.Pool)
+}
+
+// PrintImage will print an image and all of its attributes.
+func PrintImage(t *testing.T, image images.Image) {
+ t.Logf("ID: %s", image.ID)
+ t.Logf("Name: %s", image.Name)
+ t.Logf("MinDisk: %d", image.MinDisk)
+ t.Logf("MinRAM: %d", image.MinRAM)
+ t.Logf("Status: %s", image.Status)
+ t.Logf("Progress: %d", image.Progress)
+ t.Logf("Metadata: %#v", image.Metadata)
+ t.Logf("Created: %s", image.Created)
+ t.Logf("Updated: %s", image.Updated)
+}
+
+// PrintKeyPair will print keypair and all of its attributes.
+func PrintKeyPair(t *testing.T, keypair *keypairs.KeyPair) {
+ t.Logf("Name: %s", keypair.Name)
+ t.Logf("Fingerprint: %s", keypair.Fingerprint)
+ t.Logf("Public Key: %s", keypair.PublicKey)
+ t.Logf("Private Key: %s", keypair.PrivateKey)
+ t.Logf("UserID: %s", keypair.UserID)
+}
+
+// PrintNetwork will print an os-networks based network and all of its attributes.
+func PrintNetwork(t *testing.T, network *networks.Network) {
+ t.Logf("Bridge: %s", network.Bridge)
+ t.Logf("BridgeInterface: %s", network.BridgeInterface)
+ t.Logf("Broadcast: %s", network.Broadcast)
+ t.Logf("CIDR: %s", network.CIDR)
+ t.Logf("CIDRv6: %s", network.CIDRv6)
+ t.Logf("CreatedAt: %v", network.CreatedAt)
+ t.Logf("Deleted: %t", network.Deleted)
+ t.Logf("DeletedAt: %v", network.DeletedAt)
+ t.Logf("DHCPStart: %s", network.DHCPStart)
+ t.Logf("DNS1: %s", network.DNS1)
+ t.Logf("DNS2: %s", network.DNS2)
+ t.Logf("Gateway: %s", network.Gateway)
+ t.Logf("Gatewayv6: %s", network.Gatewayv6)
+ t.Logf("Host: %s", network.Host)
+ t.Logf("ID: %s", network.ID)
+ t.Logf("Injected: %t", network.Injected)
+ t.Logf("Label: %s", network.Label)
+ t.Logf("MultiHost: %t", network.MultiHost)
+ t.Logf("Netmask: %s", network.Netmask)
+ t.Logf("Netmaskv6: %s", network.Netmaskv6)
+ t.Logf("Priority: %d", network.Priority)
+ t.Logf("ProjectID: %s", network.ProjectID)
+ t.Logf("RXTXBase: %d", network.RXTXBase)
+ t.Logf("UpdatedAt: %v", network.UpdatedAt)
+ t.Logf("VLAN: %d", network.VLAN)
+ t.Logf("VPNPrivateAddress: %s", network.VPNPrivateAddress)
+ t.Logf("VPNPublicAddress: %s", network.VPNPublicAddress)
+ t.Logf("VPNPublicPort: %d", network.VPNPublicPort)
+}
+
+// PrintQuotaSet will print a quota set and all of its attributes.
+func PrintQuotaSet(t *testing.T, quotaSet *quotasets.QuotaSet) {
+ t.Logf("instances: %d\n", quotaSet.Instances)
+ t.Logf("cores: %d\n", quotaSet.Cores)
+ t.Logf("ram: %d\n", quotaSet.Ram)
+ t.Logf("key_pairs: %d\n", quotaSet.KeyPairs)
+ t.Logf("metadata_items: %d\n", quotaSet.MetadataItems)
+ t.Logf("security_groups: %d\n", quotaSet.SecurityGroups)
+ t.Logf("security_group_rules: %d\n", quotaSet.SecurityGroupRules)
+ t.Logf("fixed_ips: %d\n", quotaSet.FixedIps)
+ t.Logf("floating_ips: %d\n", quotaSet.FloatingIps)
+ t.Logf("injected_file_content_bytes: %d\n", quotaSet.InjectedFileContentBytes)
+ t.Logf("injected_file_path_bytes: %d\n", quotaSet.InjectedFilePathBytes)
+ t.Logf("injected_files: %d\n", quotaSet.InjectedFiles)
+}
+
+// PrintSecurityGroup will print a security group and all of its attributes and rules.
+func PrintSecurityGroup(t *testing.T, securityGroup *secgroups.SecurityGroup) {
+ t.Logf("ID: %s", securityGroup.ID)
+ t.Logf("Name: %s", securityGroup.Name)
+ t.Logf("Description: %s", securityGroup.Description)
+ t.Logf("Tenant ID: %s", securityGroup.TenantID)
+ t.Logf("Rules:")
+
+ for _, rule := range securityGroup.Rules {
+ t.Logf("\tID: %s", rule.ID)
+ t.Logf("\tFrom Port: %d", rule.FromPort)
+ t.Logf("\tTo Port: %d", rule.ToPort)
+ t.Logf("\tIP Protocol: %s", rule.IPProtocol)
+ t.Logf("\tIP Range: %s", rule.IPRange.CIDR)
+ t.Logf("\tParent Group ID: %s", rule.ParentGroupID)
+ t.Logf("\tGroup Tenant ID: %s", rule.Group.TenantID)
+ t.Logf("\tGroup Name: %s", rule.Group.Name)
+ }
+}
+
+// PrintServerGroup will print a server group and all of its attributes.
+func PrintServerGroup(t *testing.T, serverGroup *servergroups.ServerGroup) {
+ t.Logf("ID: %s", serverGroup.ID)
+ t.Logf("Name: %s", serverGroup.Name)
+ t.Logf("Policies: %#v", serverGroup.Policies)
+ t.Logf("Members: %#v", serverGroup.Members)
+ t.Logf("Metadata: %#v", serverGroup.Metadata)
+}
+
+// PrintTenantNetwork will print an os-tenant-networks based network and all of its attributes.
+func PrintTenantNetwork(t *testing.T, network *tenantnetworks.Network) {
+ t.Logf("ID: %s", network.ID)
+ t.Logf("Name: %s", network.Name)
+ t.Logf("CIDR: %s", network.CIDR)
+}
+
+// PrintVolumeAttachment will print a volume attachment and all of its attributes.
+func PrintVolumeAttachment(t *testing.T, volumeAttachment *volumeattach.VolumeAttachment) {
+ t.Logf("ID: %s", volumeAttachment.ID)
+ t.Logf("Device: %s", volumeAttachment.Device)
+ t.Logf("VolumeID: %s", volumeAttachment.VolumeID)
+ t.Logf("ServerID: %s", volumeAttachment.ServerID)
+}