VM Import VMs Don't Copy Source Descriptions

- Upgrade gophercloud to v2.5.0.
- Migrate code according to https://github.com/gophercloud/gophercloud/blob/main/docs/MIGRATING.md
- Set the `compute` microversion to 2.19 to get the server description.

Related to: https://github.com/harvester/harvester/issues/6464

Signed-off-by: Volker Theile <vtheile@suse.com>
This commit is contained in:
Volker Theile 2025-02-18 11:09:35 +01:00 committed by Gaurav Mehta
parent 4659913073
commit 5288366b56
4 changed files with 99 additions and 46 deletions

4
go.mod
View File

@ -4,7 +4,7 @@ go 1.23.4
require ( require (
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/gophercloud/gophercloud v1.11.0 github.com/gophercloud/gophercloud/v2 v2.5.0
github.com/harvester/harvester v1.3.0 github.com/harvester/harvester v1.3.0
github.com/k8snetworkplumbingwg/network-attachment-definition-client v1.3.0 github.com/k8snetworkplumbingwg/network-attachment-definition-client v1.3.0
github.com/onsi/ginkgo/v2 v2.22.0 github.com/onsi/ginkgo/v2 v2.22.0
@ -96,7 +96,7 @@ require (
golang.org/x/mod v0.21.0 // indirect golang.org/x/mod v0.21.0 // indirect
golang.org/x/net v0.33.0 // indirect golang.org/x/net v0.33.0 // indirect
golang.org/x/oauth2 v0.24.0 // indirect golang.org/x/oauth2 v0.24.0 // indirect
golang.org/x/sys v0.28.0 // indirect golang.org/x/sys v0.30.0 // indirect
golang.org/x/term v0.27.0 // indirect golang.org/x/term v0.27.0 // indirect
golang.org/x/text v0.21.0 // indirect golang.org/x/text v0.21.0 // indirect
golang.org/x/time v0.5.0 // indirect golang.org/x/time v0.5.0 // indirect

9
go.sum
View File

@ -553,8 +553,8 @@ github.com/googleapis/gnostic v0.4.0/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1a
github.com/gophercloud/gophercloud v0.3.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= github.com/gophercloud/gophercloud v0.3.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
github.com/gophercloud/gophercloud v0.6.0/go.mod h1:GICNByuaEBibcjmjvI7QvYJSZEbGkcYwAR7EZK2WMqM= github.com/gophercloud/gophercloud v0.6.0/go.mod h1:GICNByuaEBibcjmjvI7QvYJSZEbGkcYwAR7EZK2WMqM=
github.com/gophercloud/gophercloud v0.10.0/go.mod h1:gmC5oQqMDOMO1t1gq5DquX/yAU808e/4mzjjDA76+Ss= github.com/gophercloud/gophercloud v0.10.0/go.mod h1:gmC5oQqMDOMO1t1gq5DquX/yAU808e/4mzjjDA76+Ss=
github.com/gophercloud/gophercloud v1.11.0 h1:ls0O747DIq1D8SUHc7r2vI8BFbMLeLFuENaAIfEx7OM= github.com/gophercloud/gophercloud/v2 v2.5.0 h1:DubPfC43gsZiGZ9LT1IJflVMm+0rck0ejoPsH8D5rqk=
github.com/gophercloud/gophercloud v1.11.0/go.mod h1:aAVqcocTSXh2vYFZ1JTvx4EQmfgzxRcNupUfxZbBNDM= github.com/gophercloud/gophercloud/v2 v2.5.0/go.mod h1:Ki/ILhYZr/5EPebrPL9Ej+tUg4lqx71/YH2JWVeU+Qk=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20191106031601-ce3c9ade29de/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20191106031601-ce3c9ade29de/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
@ -1186,7 +1186,6 @@ golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20200422194213-44a606286825/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200422194213-44a606286825/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@ -1384,8 +1383,8 @@ golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=

View File

@ -13,17 +13,16 @@ import (
"time" "time"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/v2"
"github.com/gophercloud/gophercloud/openstack" "github.com/gophercloud/gophercloud/v2/openstack"
"github.com/gophercloud/gophercloud/openstack/blockstorage/extensions/volumeactions" "github.com/gophercloud/gophercloud/v2/openstack/blockstorage/v2/volumes"
"github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes" "github.com/gophercloud/gophercloud/v2/openstack/blockstorage/v3/snapshots"
"github.com/gophercloud/gophercloud/openstack/blockstorage/v3/snapshots" "github.com/gophercloud/gophercloud/v2/openstack/compute/v2/flavors"
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/startstop" "github.com/gophercloud/gophercloud/v2/openstack/compute/v2/servers"
"github.com/gophercloud/gophercloud/openstack/compute/v2/flavors" "github.com/gophercloud/gophercloud/v2/openstack/image/v2/imagedata"
"github.com/gophercloud/gophercloud/openstack/compute/v2/servers" "github.com/gophercloud/gophercloud/v2/openstack/image/v2/images"
"github.com/gophercloud/gophercloud/openstack/imageservice/v2/imagedata" "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/networks"
"github.com/gophercloud/gophercloud/openstack/imageservice/v2/images" "github.com/gophercloud/gophercloud/v2/openstack/utils"
"github.com/gophercloud/gophercloud/openstack/networking/v2/networks"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/api/resource"
@ -37,8 +36,9 @@ import (
const ( const (
NotUniqueName = "notUniqueName" NotUniqueName = "notUniqueName"
NotServerFound = "noServerFound" NotServerFound = "noServerFound"
pollingTimeout = 2 * 60 * 60 // in seconds pollingTimeout = 2 * 60 * 60 * time.Second
annotationDescription = "field.cattle.io/description" annotationDescription = "field.cattle.io/description"
computeMicroversion = "2.19"
) )
type Client struct { type Client struct {
@ -62,7 +62,12 @@ type ExtendedVolume struct {
// - https://docs.openstack.org/api-ref/compute/?expanded=list-all-metadata-detail%2Ccreate-server-detail#show-server-details // - https://docs.openstack.org/api-ref/compute/?expanded=list-all-metadata-detail%2Ccreate-server-detail#show-server-details
type ExtendedServer struct { type ExtendedServer struct {
servers.Server servers.Server
Description string `json:"description,omitempty"` ServerDescription
}
type ServerDescription struct {
// This requires microversion 2.19 or later.
Description string `json:"description"`
} }
// NewClient will generate a GopherCloud client // NewClient will generate a GopherCloud client
@ -117,7 +122,7 @@ func NewClient(ctx context.Context, endpoint string, region string, secret *core
return nil, fmt.Errorf("error generating new client: %v", err) return nil, fmt.Errorf("error generating new client: %v", err)
} }
client.HTTPClient.Transport = tr client.HTTPClient.Transport = tr
err = openstack.Authenticate(client, authOpts) err = openstack.Authenticate(ctx, client, authOpts)
if err != nil { if err != nil {
return nil, fmt.Errorf("error authenticated client: %v", err) return nil, fmt.Errorf("error authenticated client: %v", err)
} }
@ -132,7 +137,24 @@ func NewClient(ctx context.Context, endpoint string, region string, secret *core
return nil, fmt.Errorf("error generating compute client: %v", err) return nil, fmt.Errorf("error generating compute client: %v", err)
} }
imageClient, err := openstack.NewImageServiceV2(client, endPointOpts) // Try to set the `compute` microversion to 2.19 to get the server description.
// https://docs.openstack.org/nova/latest/reference/api-microversion-history.html
supportedMicroversions, err := utils.GetSupportedMicroversions(ctx, computeClient)
if err != nil {
return nil, fmt.Errorf("failed to fetch supported microversions from compute client: %v", err)
}
supported, err := supportedMicroversions.IsSupported(computeMicroversion)
if err == nil && supported {
logrus.WithFields(logrus.Fields{
"type": computeClient.Type,
"minMicroversion": fmt.Sprintf("%d.%d", supportedMicroversions.MinMajor, supportedMicroversions.MinMinor),
"maxMicroversion": fmt.Sprintf("%d.%d", supportedMicroversions.MaxMajor, supportedMicroversions.MaxMinor),
"microversion": computeMicroversion,
}).Debug("Setting custom microversion")
computeClient.Microversion = computeMicroversion
}
imageClient, err := openstack.NewImageV2(client, endPointOpts)
if err != nil { if err != nil {
return nil, fmt.Errorf("error generating image client: %v", err) return nil, fmt.Errorf("error generating image client: %v", err)
} }
@ -156,7 +178,7 @@ func NewClient(ctx context.Context, endpoint string, region string, secret *core
func (c *Client) Verify() error { func (c *Client) Verify() error {
pg := servers.List(c.computeClient, servers.ListOpts{}) pg := servers.List(c.computeClient, servers.ListOpts{})
allPg, err := pg.AllPages() allPg, err := pg.AllPages(c.ctx)
if err != nil { if err != nil {
return fmt.Errorf("error generating all pages: %v", err) return fmt.Errorf("error generating all pages: %v", err)
} }
@ -189,7 +211,7 @@ func (c *Client) PreFlightChecks(vm *migration.VirtualMachineImport) (err error)
}).Info("Checking the source network as part of the preflight checks") }).Info("Checking the source network as part of the preflight checks")
pgr := networks.List(c.networkClient, networks.ListOpts{Name: nm.SourceNetwork}) pgr := networks.List(c.networkClient, networks.ListOpts{Name: nm.SourceNetwork})
allPgs, err := pgr.AllPages() allPgs, err := pgr.AllPages(c.ctx)
if err != nil { if err != nil {
return fmt.Errorf("error while generating all pages during querying source network '%s': %v", nm.SourceNetwork, err) return fmt.Errorf("error while generating all pages during querying source network '%s': %v", nm.SourceNetwork, err)
} }
@ -215,7 +237,7 @@ func (c *Client) ExportVirtualMachine(vm *migration.VirtualMachineImport) error
imageName := fmt.Sprintf("import-controller-%s-%d", vm.Spec.VirtualMachineName, vIndex) imageName := fmt.Sprintf("import-controller-%s-%d", vm.Spec.VirtualMachineName, vIndex)
// create snapshot for volume // create snapshot for volume
snapInfo, err := snapshots.Create(c.storageClient, snapshots.CreateOpts{ snapInfo, err := snapshots.Create(c.ctx, c.storageClient, snapshots.CreateOpts{
Name: imageName, Name: imageName,
VolumeID: v.ID, VolumeID: v.ID,
Force: true, Force: true,
@ -235,14 +257,17 @@ func (c *Client) ExportVirtualMachine(vm *migration.VirtualMachineImport) error
"snapshot.size": snapInfo.Size, "snapshot.size": snapInfo.Size,
}).Info("Waiting for snapshot to be available") }).Info("Waiting for snapshot to be available")
if err := snapshots.WaitForStatus(c.storageClient, snapInfo.ID, "available", pollingTimeout); err != nil { ctxWithTimeout1, cancel1 := context.WithTimeout(c.ctx, pollingTimeout)
defer cancel1()
if err := snapshots.WaitForStatus(ctxWithTimeout1, c.storageClient, snapInfo.ID, "available"); err != nil {
return fmt.Errorf("timeout waiting for snapshot %s to be available: %v", snapInfo.ID, err) return fmt.Errorf("timeout waiting for snapshot %s to be available: %v", snapInfo.ID, err)
} }
volObj, err := volumes.Create(c.storageClient, volumes.CreateOpts{ volObj, err := volumes.Create(c.ctx, c.storageClient, volumes.CreateOpts{
SnapshotID: snapInfo.ID, SnapshotID: snapInfo.ID,
Size: snapInfo.Size, Size: snapInfo.Size,
}).Extract() }, nil).Extract()
if err != nil { if err != nil {
return err return err
} }
@ -260,7 +285,10 @@ func (c *Client) ExportVirtualMachine(vm *migration.VirtualMachineImport) error
"retryDelay": c.options.UploadImageRetryDelay, "retryDelay": c.options.UploadImageRetryDelay,
}).Info("Waiting for volume to be available") }).Info("Waiting for volume to be available")
if err := volumes.WaitForStatus(c.storageClient, volObj.ID, "available", pollingTimeout); err != nil { ctxWithTimeout2, cancel2 := context.WithTimeout(c.ctx, pollingTimeout)
defer cancel2()
if err := volumes.WaitForStatus(ctxWithTimeout2, c.storageClient, volObj.ID, "available"); err != nil {
return fmt.Errorf("timeout waiting for volume %s to be available: %v", volObj.ID, err) return fmt.Errorf("timeout waiting for volume %s to be available: %v", volObj.ID, err)
} }
@ -273,17 +301,17 @@ func (c *Client) ExportVirtualMachine(vm *migration.VirtualMachineImport) error
"attachedVolumeID": v.ID, "attachedVolumeID": v.ID,
}).Info("Creating a new image from a volume") }).Info("Creating a new image from a volume")
volImage, err := volumeactions.UploadImage(c.storageClient, volObj.ID, volumeactions.UploadImageOpts{ volImage, err := volumes.UploadImage(c.ctx, c.storageClient, volObj.ID, volumes.UploadImageOpts{
ImageName: imageName, ImageName: imageName,
DiskFormat: "raw", DiskFormat: "raw",
}).Extract() }).Extract()
if err != nil { if err != nil {
return err return fmt.Errorf("error while uploading volume image: %v", err)
} }
// wait for image to be ready // wait for image to be ready
for i := 0; i < c.options.UploadImageRetryCount; i++ { for i := 0; i < c.options.UploadImageRetryCount; i++ {
imgObj, err := images.Get(c.imageClient, volImage.ImageID).Extract() imgObj, err := images.Get(c.ctx, c.imageClient, volImage.ImageID).Extract()
if err != nil { if err != nil {
return fmt.Errorf("error checking status of volume image %s: %v", volImage.ImageID, err) return fmt.Errorf("error checking status of volume image %s: %v", volImage.ImageID, err)
} }
@ -309,9 +337,9 @@ func (c *Client) ExportVirtualMachine(vm *migration.VirtualMachineImport) error
"imageID": volImage.ImageID, "imageID": volImage.ImageID,
}).Info("Downloading an image") }).Info("Downloading an image")
contents, err := imagedata.Download(c.imageClient, volImage.ImageID).Extract() contents, err := imagedata.Download(c.ctx, c.imageClient, volImage.ImageID).Extract()
if err != nil { if err != nil {
return err return fmt.Errorf("error downloading volume image %s: %v", volImage.ImageID, err)
} }
rawImageFileName := generateRawImageFileName(vm.Status.ImportedVirtualMachineName, vIndex) rawImageFileName := generateRawImageFileName(vm.Status.ImportedVirtualMachineName, vIndex)
@ -321,21 +349,22 @@ func (c *Client) ExportVirtualMachine(vm *migration.VirtualMachineImport) error
"namespace": vm.Namespace, "namespace": vm.Namespace,
"spec.virtualMachineName": vm.Spec.VirtualMachineName, "spec.virtualMachineName": vm.Spec.VirtualMachineName,
"volume.imageID": volImage.ImageID, "volume.imageID": volImage.ImageID,
"rawImageFileName": rawImageFileName,
}).Info("Downloading RAW image") }).Info("Downloading RAW image")
err = writeRawImageFile(filepath.Join(server.TempDir(), rawImageFileName), contents) err = writeRawImageFile(filepath.Join(server.TempDir(), rawImageFileName), contents)
if err != nil { if err != nil {
return err return fmt.Errorf("error downloading RAW image %s: %v", rawImageFileName, err)
} }
if err := volumes.Delete(c.storageClient, volObj.ID, volumes.DeleteOpts{}).ExtractErr(); err != nil { if err := volumes.Delete(c.ctx, c.storageClient, volObj.ID, volumes.DeleteOpts{}).ExtractErr(); err != nil {
return fmt.Errorf("error deleting volume %s: %v", volObj.ID, err) return fmt.Errorf("error deleting volume %s: %v", volObj.ID, err)
} }
if err := snapshots.Delete(c.storageClient, snapInfo.ID).ExtractErr(); err != nil { if err := snapshots.Delete(c.ctx, c.storageClient, snapInfo.ID).ExtractErr(); err != nil {
return fmt.Errorf("error deleting snapshot %s: %v", snapInfo.ID, err) return fmt.Errorf("error deleting snapshot %s: %v", snapInfo.ID, err)
} }
if err := images.Delete(c.imageClient, volImage.ImageID).ExtractErr(); err != nil { if err := images.Delete(c.ctx, c.imageClient, volImage.ImageID).ExtractErr(); err != nil {
return fmt.Errorf("error deleting image %s: %v", volImage.ImageID, err) return fmt.Errorf("error deleting image %s: %v", volImage.ImageID, err)
} }
@ -361,7 +390,7 @@ func (c *Client) PowerOffVirtualMachine(vm *migration.VirtualMachineImport) erro
return err return err
} }
if !ok { if !ok {
return startstop.Stop(c.computeClient, serverUUID).ExtractErr() return servers.Stop(c.ctx, c.computeClient, serverUUID).ExtractErr()
} }
return nil return nil
} }
@ -387,7 +416,7 @@ func (c *Client) GenerateVirtualMachine(vm *migration.VirtualMachineImport) (*ku
return nil, fmt.Errorf("error finding VM in GenerateVirtualMachine: %v", err) return nil, fmt.Errorf("error finding VM in GenerateVirtualMachine: %v", err)
} }
flavorObj, err := flavors.Get(c.computeClient, vmObj.Flavor["id"].(string)).Extract() flavorObj, err := flavors.Get(c.ctx, c.computeClient, vmObj.Flavor["id"].(string)).Extract()
if err != nil { if err != nil {
return nil, fmt.Errorf("error looking up flavor: %v", err) return nil, fmt.Errorf("error looking up flavor: %v", err)
} }
@ -410,10 +439,10 @@ func (c *Client) GenerateVirtualMachine(vm *migration.VirtualMachineImport) (*ku
} }
if vmObj.Description != "" { if vmObj.Description != "" {
if newVM.GetAnnotations() == nil { if newVM.Annotations == nil {
newVM.Annotations = make(map[string]string) newVM.Annotations = make(map[string]string)
} }
newVM.ObjectMeta.Annotations[annotationDescription] = vmObj.Description newVM.Annotations[annotationDescription] = vmObj.Description
} }
vmSpec := kubevirt.VirtualMachineSpec{ vmSpec := kubevirt.VirtualMachineSpec{
@ -594,7 +623,7 @@ func (c *Client) checkOrGetUUID(input string) (string, error) {
}*/ }*/
pg := servers.List(c.computeClient, servers.ListOpts{Name: input}) pg := servers.List(c.computeClient, servers.ListOpts{Name: input})
allPg, err := pg.AllPages() allPg, err := pg.AllPages(c.ctx)
if err != nil { if err != nil {
return "", fmt.Errorf("error generating all pages in checkorgetuuid :%v", err) return "", fmt.Errorf("error generating all pages in checkorgetuuid :%v", err)
} }
@ -634,9 +663,11 @@ func (c *Client) findVM(name string) (*ExtendedServer, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
sr := servers.Get(c.computeClient, parsedUUID) sr := servers.Get(c.ctx, c.computeClient, parsedUUID)
var s ExtendedServer var s ExtendedServer
err = sr.ExtractInto(&s) err = sr.ExtractInto(&s)
return &s, err return &s, err
} }
@ -664,7 +695,7 @@ func (c *Client) ImageFirmwareSettings(instance *servers.Server) (bool, bool, bo
var imageID string var imageID string
var uefiType, tpmEnabled, secureBoot bool var uefiType, tpmEnabled, secureBoot bool
for _, v := range instance.AttachedVolumes { for _, v := range instance.AttachedVolumes {
resp := volumes.Get(c.storageClient, v.ID) resp := volumes.Get(c.ctx, c.storageClient, v.ID)
var volInfo volumes.Volume var volInfo volumes.Volume
if err := resp.ExtractIntoStructPtr(&volInfo, "volume"); err != nil { if err := resp.ExtractIntoStructPtr(&volInfo, "volume"); err != nil {
return uefiType, tpmEnabled, secureBoot, fmt.Errorf("error extracting volume info for volume %s: %v", v.ID, err) return uefiType, tpmEnabled, secureBoot, fmt.Errorf("error extracting volume info for volume %s: %v", v.ID, err)
@ -679,7 +710,7 @@ func (c *Client) ImageFirmwareSettings(instance *servers.Server) (bool, bool, bo
} }
} }
imageInfo, err := images.Get(c.imageClient, imageID).Extract() imageInfo, err := images.Get(c.ctx, c.imageClient, imageID).Extract()
if err != nil { if err != nil {
return uefiType, tpmEnabled, secureBoot, fmt.Errorf("error getting image details for image %s: %v", imageID, err) return uefiType, tpmEnabled, secureBoot, fmt.Errorf("error getting image details for image %s: %v", imageID, err)
} }

View File

@ -6,6 +6,7 @@ import (
"os" "os"
"testing" "testing"
"github.com/gophercloud/gophercloud/v2/openstack/compute/v2/servers"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -200,3 +201,25 @@ func Test_SourceGetOptions(t *testing.T) {
assert.Equal(options.UploadImageRetryDelay, tc.expected.UploadImageRetryDelay, tc.desc) assert.Equal(options.UploadImageRetryDelay, tc.expected.UploadImageRetryDelay, tc.desc)
} }
} }
func Test_ExtendedServer(t *testing.T) {
assert := require.New(t)
var dejson any
sejson := []byte(`{"server": {"id": "b3693d06-8135-4c7c-b3ea-d37b2cc6fb8f", "name": "cirros-tiny", "status": "SHUTOFF", "tenant_id": "88c800f12d7d4e4e93b2e2883aed1bf5", "user_id": "94ebd4b2c5a140dd9bc20dc5139d6823", "metadata": {}, "hostId": "d44ae638ea333eefe401ae01c9dec9add9ed7b6cad1024a3a220d1f4", "image": "", "flavor": {"id": "1", "links": [{"rel": "bookmark", "href": "http://48.151.623.42/compute/flavors/1"}]}, "created": "2025-02-18T17:07:24Z", "updated": "2025-02-18T17:25:13Z", "addresses": {"shared": [{"version": 4, "addr": "192.168.233.13", "OS-EXT-IPS:type": "fixed", "OS-EXT-IPS-MAC:mac_addr": "fa:16:3e:25:90:74"}]}, "accessIPv4": "", "accessIPv6": "", "links": [{"rel": "self", "href": "http://48.151.623.42/compute/v2.1/servers/b3693d06-8135-4c7c-b3ea-d37b2cc6fb8f"}, {"rel": "bookmark", "href": "http://48.151.623.42/compute/servers/b3693d06-8135-4c7c-b3ea-d37b2cc6fb8f"}], "OS-DCF:diskConfig": "AUTO", "OS-EXT-AZ:availability_zone": "nova", "config_drive": "", "key_name": null, "OS-SRV-USG:launched_at": "2025-02-18T17:08:02.000000", "OS-SRV-USG:terminated_at": null, "OS-EXT-SRV-ATTR:host": "opnstk-server-vm", "OS-EXT-SRV-ATTR:instance_name": "instance-00000002", "OS-EXT-SRV-ATTR:hypervisor_hostname": "opnstk-server-vm", "OS-EXT-SRV-ATTR:reservation_id": "r-j7s0gpwg", "OS-EXT-SRV-ATTR:launch_index": 0, "OS-EXT-SRV-ATTR:hostname": "cirros-test", "OS-EXT-SRV-ATTR:kernel_id": "", "OS-EXT-SRV-ATTR:ramdisk_id": "", "OS-EXT-SRV-ATTR:root_device_name": "/dev/vda", "OS-EXT-SRV-ATTR:user_data": null, "OS-EXT-STS:task_state": null, "OS-EXT-STS:vm_state": "stopped", "OS-EXT-STS:power_state": 4, "os-extended-volumes:volumes_attached": [{"id": "e6565b2e-6f99-45e8-9278-fd4b4b35a1ea", "delete_on_termination": false}], "host_status": "UP", "locked": false, "description": "test foo bar"}}`)
err := json.Unmarshal(sejson, &dejson)
if err != nil {
t.Fatal(err)
}
sr := servers.GetResult{}
sr.Body = dejson
var s ExtendedServer
err = sr.ExtractInto(&s)
assert.NoError(err, "expect no error during extract")
assert.Equal(s.Name, "cirros-tiny", "expect name to be 'cirros-tiny'")
assert.Equal(s.Status, "", "expect status to be 'SHUTOFF'")
assert.Equal(s.Description, "test foo bar", "expect description to be 'test foo bar'")
}