tests: upload & boot image in OpenStack. Closes #339
This commit is contained in:
parent
18b17c87fa
commit
f7c4dca5d5
96 changed files with 22287 additions and 1965 deletions
|
|
@ -21,8 +21,11 @@ import (
|
|||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/gophercloud/openstack"
|
||||
|
||||
"github.com/osbuild/osbuild-composer/cmd/osbuild-image-tests/azuretest"
|
||||
"github.com/osbuild/osbuild-composer/cmd/osbuild-image-tests/openstacktest"
|
||||
"github.com/osbuild/osbuild-composer/cmd/osbuild-image-tests/constants"
|
||||
"github.com/osbuild/osbuild-composer/internal/common"
|
||||
)
|
||||
|
|
@ -296,6 +299,49 @@ func testBootUsingAzure(t *testing.T, imagePath string) {
|
|||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func testBootUsingOpenStack(t *testing.T, imagePath string) {
|
||||
creds, err := openstack.AuthOptionsFromEnv()
|
||||
|
||||
// if no credentials are given, fall back to qemu
|
||||
if (creds == gophercloud.AuthOptions{}) {
|
||||
log.Print("No OpenStack credentials given, falling back to booting using qemu")
|
||||
testBootUsingQemu(t, imagePath)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
// provider is the top-level client that all OpenStack services derive from
|
||||
provider, err := openstack.AuthenticatedClient(creds)
|
||||
require.NoError(t, err)
|
||||
|
||||
// create a random test id to name all the resources used in this test
|
||||
imageName, err := generateRandomString("osbuild-image-tests-openstack-image-")
|
||||
require.NoError(t, err)
|
||||
|
||||
// the following line should be done by osbuild-composer at some point
|
||||
image, err := openstacktest.UploadImageToOpenStack(provider, imagePath, imageName)
|
||||
require.NoErrorf(t, err, "Upload to OpenStack failed, resources could have been leaked")
|
||||
require.NotNil(t, image)
|
||||
|
||||
// delete the image after the test is over
|
||||
defer func() {
|
||||
err = openstacktest.DeleteImageFromOpenStack(provider, image.ID)
|
||||
require.NoErrorf(t, err, "Cannot delete OpenStack image, resources could have been leaked")
|
||||
}()
|
||||
|
||||
// boot the uploaded image and try to connect to it
|
||||
err = withSSHKeyPair(func(privateKey, publicKey string) error {
|
||||
userData, err := createUserData(publicKey)
|
||||
require.NoErrorf(t, err, "Creating user data failed: %v", err)
|
||||
|
||||
return openstacktest.WithBootedImageInOpenStack(provider, image.ID, userData, func(address string) error {
|
||||
testSSH(t, address, privateKey, nil)
|
||||
return nil
|
||||
})
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// testBoot tests if the image is able to successfully boot
|
||||
// Before the test it boots the image respecting the specified bootType.
|
||||
// The test passes if the function is able to connect to the image via ssh
|
||||
|
|
@ -318,6 +364,9 @@ func testBoot(t *testing.T, imagePath string, bootType string) {
|
|||
case "azure":
|
||||
testBootUsingAzure(t, imagePath)
|
||||
|
||||
case "openstack":
|
||||
testBootUsingOpenStack(t, imagePath)
|
||||
|
||||
default:
|
||||
panic("unknown boot type!")
|
||||
}
|
||||
|
|
|
|||
133
cmd/osbuild-image-tests/openstacktest/openstack.go
Normal file
133
cmd/osbuild-image-tests/openstacktest/openstack.go
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
// +build integration
|
||||
|
||||
package openstacktest
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/gophercloud/openstack"
|
||||
"github.com/gophercloud/gophercloud/openstack/imageservice/v2/images"
|
||||
"github.com/gophercloud/gophercloud/openstack/imageservice/v2/imagedata"
|
||||
"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
|
||||
)
|
||||
|
||||
const WaitTimeout = 600 // in seconds
|
||||
|
||||
func UploadImageToOpenStack(p *gophercloud.ProviderClient, imagePath string, imageName string) (*images.Image, error) {
|
||||
client, err := openstack.NewImageServiceV2(p, gophercloud.EndpointOpts{
|
||||
Region: os.Getenv("OS_REGION_NAME"),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error creating ImageService client: %v", err)
|
||||
}
|
||||
|
||||
// create a new image which gives us the ID
|
||||
image, err := images.Create(client, images.CreateOpts{
|
||||
Name: imageName,
|
||||
DiskFormat: "qcow2",
|
||||
ContainerFormat: "bare",
|
||||
}).Extract()
|
||||
if err != nil {
|
||||
return image, fmt.Errorf("Creating image failed: %v", err)
|
||||
}
|
||||
|
||||
// then upload the actual binary data
|
||||
imageData, err := os.Open(imagePath)
|
||||
if err != nil {
|
||||
return image, fmt.Errorf("Error opening %s: %v", imagePath, err)
|
||||
}
|
||||
defer imageData.Close()
|
||||
|
||||
err = imagedata.Upload(client, image.ID, imageData).ExtractErr()
|
||||
if err != nil {
|
||||
return image, fmt.Errorf("Upload to OpenStack failed: %v", err)
|
||||
}
|
||||
|
||||
// wait for the status to change from Queued to Active
|
||||
err = gophercloud.WaitFor(WaitTimeout, func() (bool, error) {
|
||||
actual, err := images.Get(client, image.ID).Extract()
|
||||
return actual.Status == images.ImageStatusActive, err
|
||||
})
|
||||
if err != nil {
|
||||
return image, fmt.Errorf("Waiting for image to become Active failed: %v", err)
|
||||
}
|
||||
|
||||
return image, nil
|
||||
}
|
||||
|
||||
func DeleteImageFromOpenStack(p *gophercloud.ProviderClient, imageUUID string) error {
|
||||
client, err := openstack.NewImageServiceV2(p, gophercloud.EndpointOpts{
|
||||
Region: os.Getenv("OS_REGION_NAME"),
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error creating ImageService client: %v", err)
|
||||
}
|
||||
|
||||
err = images.Delete(client, imageUUID).ExtractErr()
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot delete the image: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func WithBootedImageInOpenStack(p *gophercloud.ProviderClient, imageID, userData string, f func(address string) error) (retErr error) {
|
||||
client, err := openstack.NewComputeV2(p, gophercloud.EndpointOpts{
|
||||
Region: os.Getenv("OS_REGION_NAME"),
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error creating Compute client: %v", err)
|
||||
}
|
||||
|
||||
server, err := servers.Create(client, servers.CreateOpts{
|
||||
Name: "osbuild-composer-vm-for-" + imageID,
|
||||
FlavorRef: "77b8cf27-be16-40d9-95b1-81db4522be1e", // ci.m1.medium.ephemeral
|
||||
Networks: []servers.Network{ // provider_net_cci_2
|
||||
servers.Network{UUID: "74e8faa7-87ba-41b2-a000-438013194814"},
|
||||
},
|
||||
ImageRef: imageID,
|
||||
UserData: []byte(userData),
|
||||
}).Extract()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Cannot create instance: %v", err)
|
||||
}
|
||||
|
||||
// cleanup
|
||||
defer func(){
|
||||
err := servers.ForceDelete(client, server.ID).ExtractErr()
|
||||
if err != nil {
|
||||
fmt.Printf("Force deleting instance %s failed: %v", server.ID, err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
// wait for the status to become Active
|
||||
err = servers.WaitForStatus(client, server.ID, "ACTIVE", WaitTimeout)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Waiting for instance %s to become Active failed: %v", server.ID, err)
|
||||
}
|
||||
|
||||
// get server details again to refresh the IP addresses
|
||||
server, err = servers.Get(client, server.ID).Extract()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Cannot get instance details: %v\n", err)
|
||||
}
|
||||
|
||||
// server.AccessIPv4 is empty so list all addresses and
|
||||
// get the first fixed one. ssh should be equally happy with v4 or v6
|
||||
var fixedIP string
|
||||
for _, networkAddresses := range server.Addresses["provider_net_cci_2"].([]interface{}) {
|
||||
address := networkAddresses.(map[string]interface{})
|
||||
if address["OS-EXT-IPS:type"] == "fixed" {
|
||||
fixedIP = address["addr"].(string)
|
||||
break
|
||||
}
|
||||
}
|
||||
if fixedIP == "" {
|
||||
return fmt.Errorf("Cannot find IP address for instance %s", server.ID)
|
||||
}
|
||||
|
||||
return f(fixedIP)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue