diff --git a/pkg/clients/compute.go b/pkg/clients/compute.go index 20891b443b..158fc8983a 100644 --- a/pkg/clients/compute.go +++ b/pkg/clients/compute.go @@ -25,6 +25,7 @@ import ( "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/availabilityzones" "github.com/gophercloud/gophercloud/openstack/compute/v2/flavors" "github.com/gophercloud/gophercloud/openstack/compute/v2/servers" + "github.com/gophercloud/gophercloud/openstack/utils" "github.com/gophercloud/utils/openstack/clientconfig" uflavors "github.com/gophercloud/utils/openstack/compute/v2/flavors" @@ -32,16 +33,21 @@ import ( ) /* -NovaMinimumMicroversion is the minimum Nova microversion supported by CAPO -2.60 corresponds to OpenStack Queens +NovaSupportedVersions is the list of Nova microversion supported by CAPO +2.60 corresponds to OpenStack Queens and 2.53 to OpenStack Pike. For the canonical description of Nova microversions, see https://docs.openstack.org/nova/latest/reference/api-microversion-history.html -CAPO uses server tags, which were added in microversion 2.52. +CAPO uses server tags, which were first added in microversion 2.26 and then refined +in 2.52 so it is possible to apply them when creating a server (which is what CAPO does). CAPO supports multiattach volume types, which were added in microversion 2.60. */ -const NovaMinimumMicroversion = "2.60" +var NovaSupportedVersions = []*utils.Version{ + {ID: "2.1", Priority: 10, Suffix: "/v2.1/"}, // Initial microversion release, same as specifying no version + {ID: "2.53", Priority: 20, Suffix: "/v2.53/"}, // Maximum in Pike + {ID: "2.60", Priority: 30, Suffix: "/v2.60/"}, // Maximum in Queens +} // ServerExt is the base gophercloud Server with extensions used by InstanceStatus. type ServerExt struct { @@ -65,14 +71,24 @@ type ComputeClient interface { type computeClient struct{ client *gophercloud.ServiceClient } // NewComputeClient returns a new compute client. -func NewComputeClient(providerClient *gophercloud.ProviderClient, providerClientOpts *clientconfig.ClientOpts) (ComputeClient, error) { +func NewComputeClient(providerClient *gophercloud.ProviderClient, providerClientOpts *clientconfig.ClientOpts, + recognizedVersions ...*utils.Version, +) (ComputeClient, error) { + // If no recognized versions are supplied, we use the defaults. + if len(recognizedVersions) == 0 { + recognizedVersions = NovaSupportedVersions + } + chosen, _, err := utils.ChooseVersion(providerClient, recognizedVersions) + if err != nil { + return nil, fmt.Errorf("failed to negotiate compute client version: %v", err) + } compute, err := openstack.NewComputeV2(providerClient, gophercloud.EndpointOpts{ Region: providerClientOpts.RegionName, }) if err != nil { return nil, fmt.Errorf("failed to create compute service client: %v", err) } - compute.Microversion = NovaMinimumMicroversion + compute.Microversion = chosen.ID return &computeClient{compute}, nil } diff --git a/pkg/cloud/services/compute/instance.go b/pkg/cloud/services/compute/instance.go index 57882f349a..647cfcd688 100644 --- a/pkg/cloud/services/compute/instance.go +++ b/pkg/cloud/services/compute/instance.go @@ -31,6 +31,7 @@ import ( "github.com/gophercloud/gophercloud/openstack/compute/v2/servers" "github.com/gophercloud/gophercloud/openstack/imageservice/v2/images" "github.com/gophercloud/gophercloud/openstack/networking/v2/ports" + "github.com/gophercloud/gophercloud/openstack/utils" "k8s.io/apimachinery/pkg/runtime" "sigs.k8s.io/cluster-api/util" @@ -345,7 +346,19 @@ func (s *Service) createInstanceImpl(eventObject runtime.Object, openStackCluste serverCreateOpts = applyServerGroupID(serverCreateOpts, instanceSpec.ServerGroupID) - server, err = s.getComputeClient().CreateServer(keypairs.CreateOptsExt{ + // Filter supported versions based on features used + recognizedMicroversions := clients.NovaSupportedVersions + supportedMicroversions := []*utils.Version{} + if len(instanceSpec.Tags) > 0 { + for i := range recognizedMicroversions { + if recognizedMicroversions[i].ID == "2.1" { + continue + } + supportedMicroversions = append(supportedMicroversions, recognizedMicroversions[i]) + } + } + + server, err = s.getComputeClient(supportedMicroversions...).CreateServer(keypairs.CreateOptsExt{ CreateOptsBuilder: serverCreateOpts, KeyName: instanceSpec.SSHKeyName, }) diff --git a/pkg/cloud/services/compute/service.go b/pkg/cloud/services/compute/service.go index b819d33e31..b248371aab 100644 --- a/pkg/cloud/services/compute/service.go +++ b/pkg/cloud/services/compute/service.go @@ -19,6 +19,8 @@ package compute import ( "fmt" + "github.com/gophercloud/gophercloud/openstack/utils" + "sigs.k8s.io/cluster-api-provider-openstack/pkg/clients" "sigs.k8s.io/cluster-api-provider-openstack/pkg/cloud/services/networking" "sigs.k8s.io/cluster-api-provider-openstack/pkg/scope" @@ -39,9 +41,9 @@ func NewService(scope scope.Scope) (*Service, error) { }, nil } -func (s Service) getComputeClient() clients.ComputeClient { +func (s Service) getComputeClient(recognizedVersions ...*utils.Version) clients.ComputeClient { if s._computeClient == nil { - computeClient, err := s.scope.NewComputeClient() + computeClient, err := s.scope.NewComputeClient(recognizedVersions...) if err != nil { return clients.NewComputeErrorClient(err) } diff --git a/pkg/scope/mock.go b/pkg/scope/mock.go index 3b8a25881e..6dbed17fe9 100644 --- a/pkg/scope/mock.go +++ b/pkg/scope/mock.go @@ -21,6 +21,7 @@ import ( "github.com/go-logr/logr" "github.com/golang/mock/gomock" + "github.com/gophercloud/gophercloud/openstack/utils" "sigs.k8s.io/controller-runtime/pkg/client" infrav1 "sigs.k8s.io/cluster-api-provider-openstack/api/v1alpha7" @@ -78,7 +79,7 @@ func (f *MockScopeFactory) NewClientScopeFromCluster(_ context.Context, _ client return f, nil } -func (f *MockScopeFactory) NewComputeClient() (clients.ComputeClient, error) { +func (f *MockScopeFactory) NewComputeClient(_ ...*utils.Version) (clients.ComputeClient, error) { return f.ComputeClient, nil } diff --git a/pkg/scope/provider.go b/pkg/scope/provider.go index e3204a9d8c..30e1ffe2d4 100644 --- a/pkg/scope/provider.go +++ b/pkg/scope/provider.go @@ -27,6 +27,7 @@ import ( "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/openstack" "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens" + "github.com/gophercloud/gophercloud/openstack/utils" osclient "github.com/gophercloud/utils/client" "github.com/gophercloud/utils/openstack/clientconfig" corev1 "k8s.io/api/core/v1" @@ -113,8 +114,8 @@ func (s *providerScope) ProjectID() string { return s.projectID } -func (s *providerScope) NewComputeClient() (clients.ComputeClient, error) { - return clients.NewComputeClient(s.providerClient, s.providerClientOpts) +func (s *providerScope) NewComputeClient(recognizedVersions ...*utils.Version) (clients.ComputeClient, error) { + return clients.NewComputeClient(s.providerClient, s.providerClientOpts, recognizedVersions...) } func (s *providerScope) NewNetworkClient() (clients.NetworkClient, error) { diff --git a/pkg/scope/scope.go b/pkg/scope/scope.go index b3f6ca728c..cb9163b348 100644 --- a/pkg/scope/scope.go +++ b/pkg/scope/scope.go @@ -20,6 +20,7 @@ import ( "context" "github.com/go-logr/logr" + "github.com/gophercloud/gophercloud/openstack/utils" "sigs.k8s.io/controller-runtime/pkg/client" infrav1 "sigs.k8s.io/cluster-api-provider-openstack/api/v1alpha7" @@ -37,7 +38,7 @@ type Factory interface { // Scope contains arguments common to most operations. type Scope interface { - NewComputeClient() (clients.ComputeClient, error) + NewComputeClient(recognizedVersions ...*utils.Version) (clients.ComputeClient, error) NewVolumeClient() (clients.VolumeClient, error) NewImageClient() (clients.ImageClient, error) NewNetworkClient() (clients.NetworkClient, error) diff --git a/test/e2e/shared/openstack.go b/test/e2e/shared/openstack.go index dca857d37e..34f1bbe969 100644 --- a/test/e2e/shared/openstack.go +++ b/test/e2e/shared/openstack.go @@ -44,6 +44,7 @@ import ( "github.com/gophercloud/gophercloud/openstack/networking/v2/networks" "github.com/gophercloud/gophercloud/openstack/networking/v2/ports" "github.com/gophercloud/gophercloud/openstack/networking/v2/subnets" + "github.com/gophercloud/gophercloud/openstack/utils" "github.com/gophercloud/utils/openstack/clientconfig" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -230,12 +231,17 @@ func DumpOpenStackServers(e2eCtx *E2EContext, filter servers.ListOpts) ([]server return nil, nil } + chosen, _, err := utils.ChooseVersion(providerClient, clients.NovaSupportedVersions) + if err != nil { + return nil, fmt.Errorf("failed to negotiate compute client version: %v", err) + } + computeClient, err := openstack.NewComputeV2(providerClient, gophercloud.EndpointOpts{Region: clientOpts.RegionName}) if err != nil { return nil, fmt.Errorf("error creating compute client: %v", err) } - computeClient.Microversion = clients.NovaMinimumMicroversion + computeClient.Microversion = chosen.ID allPages, err := servers.List(computeClient, filter).AllPages() if err != nil { return nil, fmt.Errorf("error listing servers: %v", err)