Skip to content

Commit

Permalink
WIP: Flexible Nova microversions
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
lentzi90 committed May 29, 2023
1 parent b21411b commit c38aa7f
Show file tree
Hide file tree
Showing 7 changed files with 54 additions and 14 deletions.
28 changes: 22 additions & 6 deletions pkg/clients/compute.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,23 +25,29 @@ 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"

"sigs.k8s.io/cluster-api-provider-openstack/pkg/metrics"
)

/*
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 {
Expand All @@ -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
}
Expand Down
15 changes: 14 additions & 1 deletion pkg/cloud/services/compute/instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -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,
})
Expand Down
6 changes: 4 additions & 2 deletions pkg/cloud/services/compute/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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)
}
Expand Down
3 changes: 2 additions & 1 deletion pkg/scope/mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
}

Expand Down
5 changes: 3 additions & 2 deletions pkg/scope/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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) {
Expand Down
3 changes: 2 additions & 1 deletion pkg/scope/scope.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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)
Expand Down
8 changes: 7 additions & 1 deletion test/e2e/shared/openstack.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
Expand Down

0 comments on commit c38aa7f

Please sign in to comment.