From 0b2e6ccd02fce3fb3634006a714ece9d7a324f40 Mon Sep 17 00:00:00 2001 From: Lennart Jern Date: Wed, 14 Jun 2023 12:34:18 +0300 Subject: [PATCH] WIP: Flexible Nova Microversion Set the microversion using a separate method and use integer maths instead of strings and floats when comparing versions. --- pkg/clients/compute.go | 52 ++++++++++++++++++++++++-- pkg/clients/mock/compute.go | 10 +++++ pkg/cloud/services/compute/instance.go | 16 +++----- 3 files changed, 63 insertions(+), 15 deletions(-) diff --git a/pkg/clients/compute.go b/pkg/clients/compute.go index a26c73f90b..96031e23a9 100644 --- a/pkg/clients/compute.go +++ b/pkg/clients/compute.go @@ -43,12 +43,19 @@ CAPO uses server tags, which were first added in microversion 2.26 and then refi 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. */ -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 +var NovaSupportedVersions = map[int]*utils.Version{ + 38: {ID: "2.38", Priority: 10, Suffix: "/v2.38/"}, // Maximum in Newton + 53: {ID: "2.53", Priority: 20, Suffix: "/v2.53/"}, // Maximum in Pike + 60: {ID: "2.60", Priority: 30, Suffix: "/v2.60/"}, // Maximum in Queens } +// Constants for specific microversion requirement. +// The values correspond to the decimal part of the Nova version for easy comparisons. +// For example, we require 2.53 for tagging support, so NovaTagging is set to 53. +const ( + NovaTagging = 53 +) + // ServerExt is the base gophercloud Server with extensions used by InstanceStatus. type ServerExt struct { servers.Server @@ -66,6 +73,7 @@ type ComputeClient interface { ListAttachedInterfaces(serverID string) ([]attachinterfaces.Interface, error) DeleteAttachedInterface(serverID, portID string) error + RequireMicroversion(decimal int) error } type computeClient struct{ client *gophercloud.ServiceClient } @@ -79,6 +87,18 @@ func NewComputeClient(providerClient *gophercloud.ProviderClient, providerClient return nil, fmt.Errorf("failed to create compute service client: %v", err) } + // Make an initial version negotiation. + // The caller can redo this later with more restrictions via RequireMicroversion. + supportedVersions := []*utils.Version{} + for k := range NovaSupportedVersions { + supportedVersions = append(supportedVersions, NovaSupportedVersions[k]) + } + version, _, err := utils.ChooseVersion(providerClient, supportedVersions) + if err != nil { + return nil, fmt.Errorf("failed to negotiation compute service version: %w", err) + } + compute.Microversion = version.ID + return &computeClient{compute}, nil } @@ -153,6 +173,26 @@ func (c computeClient) DeleteAttachedInterface(serverID, portID string) error { return mc.ObserveRequestIgnoreNotFoundorConflict(err) } +// RequireMicroversion negotiates the Nova microversion for the ComputeClient while taking into account +// the version requirement given. The microversion will be set to minimum `2.requiredDecimal`, +// or an error will be returned. +func (c computeClient) RequireMicroversion(requiredDecimal int) error { + supportedMicroversions := []*utils.Version{} + for decimal := range NovaSupportedVersions { + if decimal < requiredDecimal { + continue + } + supportedMicroversions = append(supportedMicroversions, NovaSupportedVersions[decimal]) + } + chosen, _, err := utils.ChooseVersion(c.client.ProviderClient, supportedMicroversions) + if err != nil { + return fmt.Errorf("failed to negotiate compute client version: %v", err) + } + fmt.Println("TODO: Add proper logging. Chose microversion %s", chosen.ID) + c.client.Microversion = chosen.ID + return nil +} + type computeErrorClient struct{ error } // NewComputeErrorClient returns a ComputeClient in which every method returns the given error. @@ -191,3 +231,7 @@ func (e computeErrorClient) ListAttachedInterfaces(_ string) ([]attachinterfaces func (e computeErrorClient) DeleteAttachedInterface(_, _ string) error { return e.error } + +func (e computeErrorClient) RequireMicroversion(_ int) error { + return e.error +} diff --git a/pkg/clients/mock/compute.go b/pkg/clients/mock/compute.go index 7204858e69..def3379ffd 100644 --- a/pkg/clients/mock/compute.go +++ b/pkg/clients/mock/compute.go @@ -171,3 +171,13 @@ func (mr *MockComputeClientMockRecorder) ListServers(arg0 interface{}) *gomock.C mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListServers", reflect.TypeOf((*MockComputeClient)(nil).ListServers), arg0) } + +// TODO +func (m *MockComputeClient) RequireMicroversion(decimal int) error { + return nil +} + +// TODO +func (m *MockComputeClientMockRecorder) RequireMicroversion(decimal int) error { + return nil +} diff --git a/pkg/cloud/services/compute/instance.go b/pkg/cloud/services/compute/instance.go index b5ad4a718f..68b7614313 100644 --- a/pkg/cloud/services/compute/instance.go +++ b/pkg/cloud/services/compute/instance.go @@ -32,7 +32,6 @@ 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" "k8s.io/apimachinery/pkg/util/wait" @@ -350,19 +349,14 @@ func (s *Service) createInstanceImpl(eventObject runtime.Object, openStackCluste serverCreateOpts = applyServerGroupID(serverCreateOpts, instanceSpec.ServerGroupID) - // Filter supported versions based on features used - recognizedMicroversions := clients.NovaSupportedVersions - supportedMicroversions := []*utils.Version{} + compute := s.getComputeClient() if len(instanceSpec.Tags) > 0 { - for i := range recognizedMicroversions { - if recognizedMicroversions[i].ID == "2.1" { - continue - } - supportedMicroversions = append(supportedMicroversions, recognizedMicroversions[i]) + err = compute.RequireMicroversion(clients.NovaTagging) + if err != nil { + return nil, fmt.Errorf("tagging is not supported by the server: %w", err) } } - - server, err = s.getComputeClient().CreateServer(keypairs.CreateOptsExt{ + server, err = compute.CreateServer(keypairs.CreateOptsExt{ CreateOptsBuilder: serverCreateOpts, KeyName: instanceSpec.SSHKeyName, })