Allow ImageRef to be empty when block device is present (#97)
This commit removes the requirement for ImageRef to be set when creating
a server. This is to enable booting from a volume to work properly.
A unit test was added to verify this is possible.
Acceptance tests were also modified to handle this.
diff --git a/acceptance/openstack/compute/v2/compute.go b/acceptance/openstack/compute/v2/compute.go
index 42a2c10..9b284c3 100644
--- a/acceptance/openstack/compute/v2/compute.go
+++ b/acceptance/openstack/compute/v2/compute.go
@@ -85,7 +85,6 @@
serverCreateOpts := servers.CreateOpts{
Name: name,
FlavorRef: choices.FlavorID,
- ImageRef: choices.ImageID,
Networks: []servers.Network{
servers.Network{UUID: networkID},
},
@@ -100,7 +99,13 @@
return server, err
}
- return server, nil
+ if err := WaitForComputeStatus(client, server, "ACTIVE"); err != nil {
+ return server, err
+ }
+
+ newServer, err := servers.Get(client, server.ID).Extract()
+
+ return newServer, nil
}
// CreateDefaultRule will create a default security group rule with a
@@ -175,6 +180,53 @@
return keyPair, nil
}
+// CreateMultiEphemeralServer works like CreateServer but is configured with
+// one or more block devices defined by passing in []bootfromvolume.BlockDevice.
+// These block devices act like block devices when booting from a volume but
+// are actually local ephemeral disks.
+// An error will be returned if a server was unable to be created.
+func CreateMultiEphemeralServer(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
+ }
+
+ if err := WaitForComputeStatus(client, server, "ACTIVE"); err != nil {
+ return server, err
+ }
+
+ newServer, err := servers.Get(client, server.ID).Extract()
+
+ return newServer, 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) {
@@ -257,7 +309,54 @@
return server, err
}
- if err = WaitForComputeStatus(client, server, "ACTIVE"); err != nil {
+ if err := WaitForComputeStatus(client, server, "ACTIVE"); err != nil {
+ return server, err
+ }
+
+ return server, nil
+}
+
+// CreateServerWithoutImageRef 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 is intentionally missing to trigger an error.
+// 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 CreateServerWithoutImageRef(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,
+ 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
}
@@ -356,7 +455,7 @@
return server, err
}
- if err = WaitForComputeStatus(client, server, "ACTIVE"); err != nil {
+ if err := WaitForComputeStatus(client, server, "ACTIVE"); err != nil {
return server, err
}
@@ -376,7 +475,7 @@
return volumeAttachment, err
}
- if err = volumes.WaitForStatus(blockClient, volume.ID, "in-use", 60); err != nil {
+ if err := volumes.WaitForStatus(blockClient, volume.ID, "in-use", 60); err != nil {
return volumeAttachment, err
}
@@ -476,7 +575,7 @@
t.Fatalf("Unable to detach volume: %v", err)
}
- if err = volumes.WaitForStatus(blockClient, volumeAttachment.ID, "available", 60); err != nil {
+ 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)