From c38aa7f44e28ab6316033555ba25d46a3b1455f0 Mon Sep 17 00:00:00 2001 From: Lennart Jern Date: Mon, 29 May 2023 10:56:35 +0300 Subject: [PATCH] WIP: Flexible Nova microversions Use a list of supported versions instead of a single hard coded one. The list can be filtered based on specific feature requirements (e.g. usage of server tags). The version to use is then picked from the intersection of this list and what the server supports. --- pkg/clients/compute.go | 28 ++++++++++++++++++++------ pkg/cloud/services/compute/instance.go | 15 +++++++++++++- pkg/cloud/services/compute/service.go | 6 ++++-- pkg/scope/mock.go | 3 ++- pkg/scope/provider.go | 5 +++-- pkg/scope/scope.go | 3 ++- test/e2e/shared/openstack.go | 8 +++++++- 7 files changed, 54 insertions(+), 14 deletions(-) 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)