cloud/gcp: introduce ComputeImageInsert() method

Introduce a new `ComputeImageInsert()` method for importing images into
GCP. It uses the `compute.Images.Insert()` API [1], which has many
advantages over the currently used way of importing images using the
CloudBuild API. The advantages are mainly that the image is imported as
is and no additional cache files or VMs are created as part of the
import process. Therefore there is no need to do additional cleanup of
cache files after importing the image.

In addition, the import itself is approximately 30% faster for RHEL
images when using the `Insert()` call.

Nevertheless the `Insert()` call accepts only gzip-ed tarball with a RAW
disk, unlike the `Import()` call, which accepts basically any virtual
disk format.

[1] https://cloud.google.com/compute/docs/reference/rest/v1/images/insert

Signed-off-by: Tomas Hozza <thozza@redhat.com>
This commit is contained in:
Tomas Hozza 2022-02-07 22:12:31 +01:00 committed by Tom Gundersen
parent e7f6b95032
commit 264e554971

View file

@ -18,6 +18,43 @@ import (
"google.golang.org/protobuf/types/known/durationpb"
)
// Guest OS Features for RHEL8 images
var GuestOsFeaturesRHEL8 []*computepb.GuestOsFeature = []*computepb.GuestOsFeature{
{Type: common.StringToPtr(computepb.GuestOsFeature_UEFI_COMPATIBLE.String())},
{Type: common.StringToPtr(computepb.GuestOsFeature_VIRTIO_SCSI_MULTIQUEUE.String())},
{Type: common.StringToPtr(computepb.GuestOsFeature_SEV_CAPABLE.String())},
}
// Guest OS Features for RHEL9 images
var GuestOsFeaturesRHEL9 []*computepb.GuestOsFeature = []*computepb.GuestOsFeature{
{Type: common.StringToPtr(computepb.GuestOsFeature_UEFI_COMPATIBLE.String())},
{Type: common.StringToPtr(computepb.GuestOsFeature_VIRTIO_SCSI_MULTIQUEUE.String())},
{Type: common.StringToPtr(computepb.GuestOsFeature_SEV_CAPABLE.String())},
{Type: common.StringToPtr(computepb.GuestOsFeature_GVNIC.String())},
}
// GuestOsFeaturesByDistro returns the the list of Guest OS Features, which
// should be used when importing an image of the specified distribution.
//
// In case the provided distribution does not have any specific Guest OS
// Features list defined, nil is returned.
func GuestOsFeaturesByDistro(distroName string) []*computepb.GuestOsFeature {
switch {
case strings.HasPrefix(distroName, "centos-8"):
fallthrough
case strings.HasPrefix(distroName, "rhel-8"):
return GuestOsFeaturesRHEL8
case strings.HasPrefix(distroName, "centos-9"):
fallthrough
case strings.HasPrefix(distroName, "rhel-9"):
return GuestOsFeaturesRHEL9
default:
return nil
}
}
// ComputeImageImport imports a previously uploaded image by submitting a Cloud Build API
// job. The job builds an image into Compute Engine from an image uploaded to the
// storage.
@ -183,6 +220,97 @@ func (g *GCP) ComputeImageImport(ctx context.Context, bucket, object, imageName,
return imageBuild, nil
}
// ComputeImageInsert imports a previously uploaded archive with raw image into Compute Engine.
//
// The image must be RAW image named 'disk.raw' inside a gzip-ed tarball.
//
// To delete the Storage object (image) used for the image import, use StorageObjectDelete().
//
// bucket - Google storage bucket name with the uploaded image archive
// object - Google storage object name of the uploaded image
// imageName - Desired image name after the import. This must be unique within the whole project.
// regions - A list of valid Google Storage regions where the resulting image should be located.
// It is possible to specify multiple regions. Also multi and dual regions are allowed.
// If not provided, the region of the used Storage object is used.
// See: https://cloud.google.com/storage/docs/locations
// guestOsFeatures - A list of features supported by the Guest OS on the imported image.
//
// Uses:
// - Compute Engine API
func (g *GCP) ComputeImageInsert(
ctx context.Context,
bucket, object, imageName string,
regions []string,
guestOsFeatures []*computepb.GuestOsFeature) (*computepb.Image, error) {
imagesClient, err := compute.NewImagesRESTClient(ctx, option.WithCredentials(g.creds))
if err != nil {
return nil, fmt.Errorf("failed to get Compute Engine Images client: %v", err)
}
defer imagesClient.Close()
operationsClient, err := compute.NewGlobalOperationsRESTClient(ctx, option.WithCredentials(g.creds))
if err != nil {
return nil, fmt.Errorf("failed to get Compute Engine Operations client: %v", err)
}
defer operationsClient.Close()
imgInsertReq := &computepb.InsertImageRequest{
Project: g.GetProjectID(),
ImageResource: &computepb.Image{
Name: &imageName,
StorageLocations: regions,
GuestOsFeatures: guestOsFeatures,
RawDisk: &computepb.RawDisk{
ContainerType: common.StringToPtr(computepb.RawDisk_TAR.String()),
Source: common.StringToPtr(fmt.Sprintf("https://storage.googleapis.com/%s/%s", bucket, object)),
},
},
}
operation, err := imagesClient.Insert(ctx, imgInsertReq)
if err != nil {
return nil, fmt.Errorf("failed to insert provided image into GCE: %v", err)
}
// wait for the operation to finish
var operationResource *computepb.Operation
for {
waitOperationReq := &computepb.WaitGlobalOperationRequest{
Operation: operation.Proto().GetName(),
Project: g.GetProjectID(),
}
operationResource, err = operationsClient.Wait(ctx, waitOperationReq)
if err != nil {
return nil, fmt.Errorf("failed to wait for an Image Import operation: %v", err)
}
// The operation finished
if operationResource.GetStatus() != computepb.Operation_RUNNING && operationResource.GetStatus() != computepb.Operation_PENDING {
break
}
}
// If the operation failed, the HttpErrorStatusCode is set to a non-zero value
if operationStatusCode := operationResource.GetHttpErrorStatusCode(); operationStatusCode != 0 {
operationErrorMsg := operationResource.GetHttpErrorMessage()
operationErrors := operationResource.GetError().GetErrors()
return nil, fmt.Errorf("failed to insert image into GCE. HTTPErrorCode:%d HTTPErrorMsg:%v Errors:%v", operationStatusCode, operationErrorMsg, operationErrors)
}
getImageReq := &computepb.GetImageRequest{
Image: imageName,
Project: g.GetProjectID(),
}
image, err := imagesClient.Get(ctx, getImageReq)
if err != nil {
return nil, fmt.Errorf("failed to get information about the imported Image: %v", err)
}
return image, nil
}
// ComputeImageURL returns an image's URL to Google Cloud Console. The method does
// not check at all, if the image actually exists or not.
func (g *GCP) ComputeImageURL(imageName string) string {